Task description

In a 2019 publication (doi:10.1016/j.cell.2019.01.031), Peter van Galen and colleagues analyzed transcriptomic profiles of bone marrow aspirates from 16 AML patients and five healthy donors.

Your first task in this exercise is to provide a concise overview of the dataset, including summary statistics such as the composition of cell-type frequencies across donors and a visualization of the data in two dimensions (e.g., using UMAP or t-SNE embeddings).

An interesting feature of the dataset is the availability of labels distinguishing malignant and non-malignant single cells. The second task is to exploit these labels to build a suitable ML classifier that predicts the normal or malignant identity of single cells. Please detail on how your classifier works, whether it is interpretable, its performance, and the train-test split you use for your machine learning experiment.

Relevance of the problem

Task 1

It is important to get a good understanding of a dataset before training any kind of machine learning algorithm on it. Important factors to be aware of are biases in the data, in particular, biases in the patient metadata.
Furthermore, the quality of the data has to be assessed, such as sample and cell quality.
Lastly, transcriptomics data is highly dimensional and in order to recognize patterns in the data, it is useful to embed the data into a 2D space that can be visualized. Embedding of data into a lower dimensional space, e.g. via PCA, also serves to remove noise.

Task 2

Depending on disease burden, there are healthy and malignant cells present in the bone marrow of patients with AML.
In order to investigate disease mechanisms of AML, we need to be able to clearly differentiate between healthy and malignant cells. Only then, we can look at what biological processes in cell differentiation are disrupted. The differentiation is also important to identify treatment targets that do not kill the remaining healthy HSPCs.
Particularly in MRD samples it is very important to identify the subset of malignant cells that is resistant/tolerant to treatment.
In AML, healthy and malignant progenitor and myeloid cells are not distinguishable by cell surface markers i.e., it is not possible to isolate healthy/malignant cells by FACS (yet).
Thus, scRNA-seq data of AML BM samples contain both groups of cells and we have thus far not been able to distinguish them based on their transcriptome.

Van Galen et al. employed a novel technology to genotype a subset of cells present in the scRNA-seq data and used these genotyped cells to build a classifier to distinguish the bulk of the cells into malignant and non-malignant cells.
However, sc genotyping comes at an extra cost and labor and is not available for all datasets. Therefore, it would be extremely useful to have a general classifier of normal vs. malignant cells in AML samples, that is applicable to other scRNA-seq datasets.

van Galen 2019 dataset

Paper

Original analysis of the data

Re-analysis of data

Data downloaded with wget https://www.dropbox.com/s/399x045zc57fiut/Seurat_AML.rds

MUTZ-3 and OCI-AML3 are AML cell lines.

BM samples are from healthy donors. Cells from one healthy donor were sorted for CD34+ (HSPCs) and CD34+CD38- (HSCs), to enrich for rare early progenitor populations.

All other samples are from patients diagnosed with AML, with different driver mutations.

AML samples were collected at day 0 and for a subset of samples at different time points post diagnosis (and post chemo).

Task 1: Exploratory data analysis

library(readxl)
library(Seurat)
library(tidyverse)
library(chameleon)
library(randomForest)

Preprocess metadata

Load scRNA-seq data object and metadata.

galen_aml <- readRDS("reanalyze-aml2019/Seurat_AML.rds")
donor_metadata <-
  readxl::read_xlsx(
    "ScienceDirect_files_21Nov2024_08-09-30.987/1-s2.0-S0092867419300947-mmc1.xlsx"
  )

Load HSPC cell types annotated by backspin cluster and add to metadata of Seurat object. From aml2019 github repo.

backspin_celltypes <-
  read_tsv("aml2019/04 Random forest classifier/BM_6915cells.BackSPIN.txt")
backspin_celltypes <-
  column_to_rownames(backspin_celltypes, var = "cell") %>% dplyr::rename(backspin_celltype = cluster)
rownames(backspin_celltypes) <- gsub("-", "\\.", rownames(backspin_celltypes))

galen_aml <- AddMetaData(galen_aml, metadata = backspin_celltypes)
head(galen_aml@meta.data)
head(donor_metadata)

Metadata included in Seurat object - orig.ident (patient ID + day from diagnosis) - NumberOfReads - CyclingScore - CyclingBinary - MutTranscripts (number of transcript for wt allele) - WtTranscripts (number of transcript for mutated allele) - PredictionRefined (classification of cells into malignant, healthy and uncertain) - CellType (classification into 21 healthy and malignant cell types) - nCount_RNA - nFeature_RNA

Remove cells and metadata associated to cell lines.

galen_aml$SampleClass <-
  ifelse(
    grepl("BM", galen_aml$orig.ident),
    "HealthyDonor",
    ifelse(
      grepl("OCI.AML3|MUTZ3", galen_aml$orig.ident),
      "CellLine",
      "AmlDonor"
    )
  )
galen_aml_donors <- subset(galen_aml, SampleClass != "CellLine")

donor_metadata <- donor_metadata %>%
  filter(!Sample %in% c("OCI-AML3", "MUTZ3"))

Correct value in blast count metadata. “1% (76% promono-cytes)” is considered as blast count of 76% in the paper (Fig. 2b).

donor_metadata$`Blast count` <-
  as.numeric(gsub("<5\\%", "0.05", gsub(
    "<1\\%",
    "0.01",
    gsub(".*76\\%.*", "0.76", donor_metadata$`Blast count`)
  )))
Warning: NAs introduced by coercion
donor_metadata$Age <- as.numeric(donor_metadata$Age)

Merge donor metadata into Seurat object metadata.

# match sample ID between Seurat object and metadata table
donor_metadata <- donor_metadata %>%
  mutate(SampleId = gsub("CD", "", gsub(" ", "\\.", gsub(
    "\\+", "p", gsub("-", "n", Sample)
  )))) %>%
  mutate(SampleId = ifelse(
    grepl("BM", SampleId),
    SampleId,
    paste(SampleId, `Days from diagnosis`, sep = ".")
  ))

# merge into seurat object metadata, preserve order of cells
tmp <-
  merge(
    rownames_to_column(galen_aml_donors@meta.data),
    donor_metadata,
    by.x = "orig.ident",
    by.y = "SampleId",
    all.x = T
  )
tmp <- column_to_rownames(tmp, "rowname")
galen_aml_donors@meta.data <- tmp[colnames(galen_aml_donors),]

Sex distribution

Tettero, J.M., Cloos, J. & Bullinger, L. Acute myeloid leukemia: does sex matter?. Leukemia 38, 2329–2331 (2024). https://doi.org/10.1038/s41375-024-02435-z

AML is more frequent in males (ca. 4.5 per 100,000) vs females (ca. 3.0 per 100,000). A number of studies showing differences in treatment response in males and females demonstrates the importance to consider sex.

The van Galen dataset only has male healthy donors and is biased towards male AML donors (62.5%). Slightly more pronounced when considering AML samples (66.7%).

Only cells from healthy donors and genotyped malignant cells from AML cells were used for training the malignant vs. normal classifier. I.e., all healthy cells in the training data were male.
This would usually result in a classification of all female cells as AML cells. However, there is also a male AML samples with loss of Y (AML707B), which makes sex more ambiguously linked to malignant status. Furthermore, expression of sex-specific genes could be low/sparse, and not all cells might be distinguishable by sex based on their expression.

# donors
p1 <- donor_metadata %>%
  select(Sample, Gender) %>%
  mutate(Sample = gsub(" .*", "", Sample)) %>%
  unique() %>%
  mutate(Group = ifelse(grepl("BM", Sample), "Healthy", "AML")) %>%
  group_by(Group, Gender) %>%
  summarize(n_donors = n()) %>%
  ggplot(aes(fill = Gender, y = n_donors, x = Group)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    legend.position = "None",
    panel.grid = element_blank()
  )
`summarise()` has grouped output by 'Group'. You can override using the `.groups` argument.
# samples
p2 <- donor_metadata %>%
  select(Sample, `Days from diagnosis`, Gender) %>%
  unique() %>%
  mutate(Group = ifelse(grepl("BM", Sample), "Healthy", "AML")) %>%
  group_by(Group, Gender) %>%
  summarize(n_samples = n()) %>%
  ggplot(aes(fill = Gender, y = n_samples, x = Group)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  theme(panel.grid = element_blank(),
        axis.text.x = element_text(angle = 45, hjust = 1))
`summarise()` has grouped output by 'Group'. You can override using the `.groups` argument.
ggpubr::ggarrange(p1, p2, align = "h", widths = c(1, 1.3))

Cancer driver mutations

Plot which sample has which cancer driver gene mutated (see Fig. 2B).
AML722B BCOR in paper Fig. 2b but not in supplementary metadata.
All samples have a unique combination of mutations.

sample_mutation_matrix <- donor_metadata %>%
  filter(
    !`RHP Mutations` %in% c("Not performed", "None Detected", "Unknown", "NA") &
      !is.na(`RHP Mutations`)
  ) %>%
  mutate(driver_mutations = `RHP Mutations`) %>%
  separate_longer_delim(cols = driver_mutations, "/// ") %>%
  mutate(driver_mutations = gsub(" .*", "", driver_mutations)) %>%
  # unique for samples with multiple mutations in same gene
  unique() %>%
  # count number of samples for each gene and sort by that
  group_by(driver_mutations) %>%
  mutate(n_samples = n()) %>%
  arrange(desc(n_samples)) %>%
  mutate(value = 1)

sample_mutation_matrix <- sample_mutation_matrix %>%
  mutate(
    driver_mutations = factor(
      driver_mutations,
      levels = unique(sample_mutation_matrix$driver_mutations),
      ordered = T
    ),
    Sample = factor(
      Sample,
      level = unique(sample_mutation_matrix$Sample),
      ordered = T
    )
  )

# mutations
p1 <- sample_mutation_matrix %>%
  ggplot(aes(x = driver_mutations, y = Sample)) +
  geom_tile(fill = "darkred") +
  theme_bw() +
  theme(axis.text.x = element_text(
    angle = 90,
    vjust = 0.5,
    hjust = 1
  )) +
  theme(panel.grid = element_blank())

# rearrangements
p2 <- sample_mutation_matrix %>%
  ggplot(aes(x = `Common translocation`, y = Sample)) +
  geom_tile(fill = "darkred") +
  theme_bw() +
  theme(axis.text.x = element_text(
    angle = 90,
    vjust = 0.5,
    hjust = 1
  )) +
  theme(
    panel.grid = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank()
  ) +
  ylab("")

ggpubr::ggarrange(
  p1,
  p2,
  ncol = 2,
  nrow = 1,
  align = "h",
  widths = c(4, 1)
)

Cell type composition

Cell types present in data.

unique(galen_aml_donors$CellType)
 [1] HSC          Prog         earlyEry     lateEry      GMP          ProMono      Mono         cDC          pDC          ProB         Plasma       T            CTL          B           
[15] NK           HSC-like     Prog-like    GMP-like     ProMono-like Mono-like    cDC-like    
Levels: HSC Prog earlyEry lateEry GMP ProMono Mono cDC pDC ProB B Plasma T CTL NK HSC-like Prog-like GMP-like ProMono-like Mono-like cDC-like

New metadata column whether cell is a cancer celltype or healthy.

Color scheme to avoid rainbow colors.

cell_types <- unique(galen_aml_donors@meta.data$CellType)
cell_type_colors <-
  sample(chameleon::distinct_colors(n = length(cell_types))$name,
         size = length(cell_types))
cell_type_colors <- setNames(cell_type_colors, cell_types)

Create more “coarse” cell type labels. Lymphoid and erythroid (non-malignant only), and Myeloid/HSC, Myeloid/HSC-like.

lymphoid_celltypes <- c("Plasma",
                        "NK",
                        "ProB",
                        "B",
                        "T",
                        "CTL",
                        "pDC")
galen_aml_donors@meta.data <- galen_aml_donors@meta.data %>%
  mutate(IsCancerCelltype = ifelse(grepl("-like", CellType), T, F)) %>%
  mutate(CellTypeGeneral = ifelse(
    CellType %in% lymphoid_celltypes,
    "Lymphoid",
    ifelse(
      IsCancerCelltype,
      "Myeloid/HSPC-like",
      ifelse(grepl("Ery", CellType), "Erythroid", "Myeloid/HSPC")
    )
  ))

Visualize cell type proportions across samples.

galen_aml_donors@meta.data %>%
  group_by(orig.ident) %>%
  count(CellType) %>%
  ggplot(aes(fill = CellType, y = n, x = orig.ident)) +
  geom_bar(position = "fill", stat = "identity") +
  scale_fill_manual(values = cell_type_colors[unique(galen_aml_donors@meta.data$CellType)]) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())


galen_aml_donors@meta.data %>%
  group_by(orig.ident) %>%
  count(IsCancerCelltype) %>%
  ggplot(aes(fill = IsCancerCelltype, y = n, x = orig.ident)) +
  geom_bar(position = "fill", stat = "identity") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())


galen_aml_donors@meta.data %>%
  group_by(orig.ident) %>%
  count(PredictionRefined) %>%
  ggplot(aes(fill = PredictionRefined, y = n, x = orig.ident)) +
  geom_bar(position = "fill", stat = "identity") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())


galen_aml_donors@meta.data %>%
  group_by(orig.ident) %>%
  count(CellTypeGeneral) %>%
  ggplot(aes(fill = CellTypeGeneral, y = n, x = orig.ident)) +
  geom_bar(position = "fill", stat = "identity") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())

Myeloid and HSPCs are the cells that are difficult to distinguish between healthy and malignant in AML cells. This is the actual difficult prediction task of the classifier. Visualize the proportion of these cells in the samples.

galen_aml_donors@meta.data %>%
  filter(CellTypeGeneral %in% c("Myeloid/HSPC", "Myeloid/HSPC-like")) %>%
  group_by(orig.ident, CellTypeGeneral) %>%
  summarize(n_cells = n()) %>%
  ggplot(aes(x = orig.ident, y = n_cells, fill = CellTypeGeneral)) +
  geom_bar(stat = "identity", position = "fill") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.

Proportion of cycling cells is not higher in AML samples at diagnosis than in healthy samples. AML is a disease of differentiation. The accumulation of blasts in the bone marrow is the problem, not hyper proliferation, as in other cancers.

galen_aml_donors@meta.data %>%
  filter(`Days from diagnosis` == "D0" |
           SampleClass == "HealthyDonor") %>%
  group_by(orig.ident, SampleClass) %>%
  count(CyclingBinary) %>%
  ggplot(aes(fill = CyclingBinary, y = n, x = orig.ident)) +
  geom_bar(position = "fill", stat = "identity") +
  facet_grid( ~ SampleClass, scales = "free_x", space = "free") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1), panel.grid = element_blank())

Genotype information

Classify cells based on their genotype information into malignant and healthy cells.

I classify all cells with a mutant transcript detected as malignant cells.
All other cells from AML samples with a wt allele detected could be classified as healthy.
However, this is imperfect, especially for heterozygous dominant negative mutations.
Furthermore, due to clonality, there might be cells with wt allele detected and mut allele missed.
Both can lead to false positive healthy cells.

Therefore, only using cells from AML donors genotyped as mutant and healthy cells from healthy donors in the classifier (as done by van Galen).

“The second classifier is used for determining if a cell for which we did not detect a mutant transcript is malignant or normal, based on its similarity to normal and malignant cells (i.e., cells from healthy BM and HSC to myeloid-like cells from tumor samples for which we detected mutant transcripts).”

galen_aml_donors@meta.data <- galen_aml_donors@meta.data %>%
  mutate(genotype = ifelse(
    !is.na(MutTranscripts) &
      MutTranscripts != "",
    "malignant",
    ifelse(
      !is.na(WtTranscripts) &
        WtTranscripts != "",
      "healthy",
      "not_detected"
    )
  ))

Plot 1: Cells with mut detected, only wt detected or no coverage, compared to expected blast count.

Plot 2: Cells usable to trian malignant vs. healthy classifier.

There are multiple samples and patients with no or almost no malignant genotyped cells that can be used for training.
Therefore, the classifier needs to be generalizable to other samples and mutations.
Overfitting to specific patients would be bad.

p1 <- galen_aml_donors@meta.data %>%
  group_by(orig.ident, genotype) %>%
  summarise(n_cells = n()) %>%
  ungroup() %>%
  ggplot(aes(fill = genotype, y = n_cells, x = orig.ident)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  theme(axis.text.x = element_blank(),
        panel.grid = element_blank()) +
  scale_x_discrete(drop = FALSE) +
  xlab(element_blank())
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.
p2 <- galen_aml_donors@meta.data %>%
  select(`Blast count`, orig.ident) %>%
  unique() %>%
  ggplot(aes(y = `Blast count`, x = orig.ident)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank()) +
  scale_x_discrete(drop = FALSE) +
  xlab(element_blank())

ggpubr::ggarrange(
  p1,
  p2,
  align = "v",
  heights = c(2, 1),
  ncol = 1,
  nrow = 2
)
Warning: Removed 6 rows containing missing values or values outside the scale range (`geom_bar()`).

p1 <- galen_aml_donors@meta.data %>%
  mutate(Sample = as.factor(Sample)) %>%
  filter(genotype == "malignant" |
           SampleClass == "HealthyDonor") %>%
  group_by(Sample, genotype) %>%
  summarise(n_cells = n()) %>%
  ungroup() %>%
  ggplot(aes(fill = genotype, y = n_cells, x = Sample)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  scale_x_discrete(drop = FALSE) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank()) + scale_x_discrete(drop = FALSE)
`summarise()` has grouped output by 'Sample'. You can override using the `.groups` argument.Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
p2 <- galen_aml_donors@meta.data %>%
  filter(genotype == "malignant" |
           SampleClass == "HealthyDonor") %>%
  group_by(orig.ident, genotype) %>%
  summarise(n_cells = n()) %>%
  ungroup() %>%
  ggplot(aes(fill = genotype, y = n_cells, x = orig.ident)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  scale_x_discrete(drop = FALSE) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank()) + scale_x_discrete(drop = FALSE)
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
ggpubr::ggarrange(
  p1,
  p2,
  align = "v",
  heights = c(1, 1),
  ncol = 1,
  nrow = 2
)

Concordance of genotype and van Galen celltype annotation in AML samples. NB: Cells with only wt transcript detected (“healthy”) in AML samples might not be actually healthy (heterozygosity, clonality).

galen_aml_donors_metadata <- galen_aml_donors@meta.data
table(
    filter(galen_aml_donors_metadata, SampleClass != "HealthyDonor")$genotype,
    filter(galen_aml_donors_metadata, SampleClass != "HealthyDonor")$CellType
  )
              
                HSC Prog earlyEry lateEry  GMP ProMono Mono  cDC  pDC ProB    B Plasma    T  CTL   NK HSC-like Prog-like GMP-like ProMono-like Mono-like cDC-like
  healthy        25   98       82     189   48     101  164   85   12    6   24    129  179  106  198       47       218      118          195       180       98
  malignant       5   11       15       6    9       8    2    9    4    1    5      2    5    5   10       39       188      114          292       129       80
  not_detected  409  444      403     872  329     413 2025  474  118   98  378    946 6201  966 1604     1850      3290     1591          929      2241     1890

Sex bias in cells that can be used to train classifier (mut transcript detected from AML donor, or from healthy donor).

p1 <- galen_aml_donors@meta.data %>%
  filter(genotype == "malignant" |
           SampleClass == "HealthyDonor") %>%
  mutate(class = ifelse(genotype == "malignant", "malignant", "healthy")) %>%
  group_by(Gender, class) %>%
  summarise(n_cells = n()) %>%
  ungroup() %>%
  ggplot(aes(x = class, y = n_cells, fill = Gender)) +
  geom_bar(position = "fill", stat = "identity") +
  # geom_bar(stat = "identity") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())
`summarise()` has grouped output by 'Gender'. You can override using the `.groups` argument.
p2 <- galen_aml_donors@meta.data %>%
  filter(genotype == "malignant" |
           SampleClass == "HealthyDonor") %>%
  mutate(class = ifelse(genotype == "malignant", "malignant", "healthy")) %>%
  group_by(Gender, class) %>%
  summarise(n_cells = n()) %>%
  ungroup() %>%
  ggplot(aes(x = Gender, y = n_cells, fill = class)) +
  geom_bar(position = "fill", stat = "identity") +
  # geom_bar(stat = "identity") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank())
`summarise()` has grouped output by 'Gender'. You can override using the `.groups` argument.
p1 | p2

Accuracy of the classifier

Check the accuracy of the van Galen classifier based on concordance with clinical blast count. This has already been checked in the paper and the answer is that the correlation is high.

# samples with unclear prediction
galen_aml_donors@meta.data %>%
  filter(PredictionRefined == "unclear") %>%
  pull(orig.ident) %>%
  unique() %>% as.character()
[1] "AML314.D0"   "AML314.D31"  "AML371.D0"   "AML371.D34"  "AML722B.D0"  "AML722B.D49" "AML997.D0"   "AML997.D35" 
pred_malignant_vs_blast_count <- galen_aml_donors@meta.data %>%
  group_by(
    orig.ident,
    PredictionRefined,
    `Blast count`,
    `Cell number`,
    Sample,
    Gender,
    # DriverMutations,
    `Days from diagnosis`
  ) %>%
  summarize(PredictionRefinedProportion = n() / `Cell number`) %>%
  filter(PredictionRefined == "malignant") %>%
  select(
    `Blast count`,
    PredictionRefinedProportion,
    orig.ident,
    Sample,
    Gender,
    # DriverMutations,
    `Days from diagnosis`
  ) %>%
  unique() %>%
  mutate(`Blast count` = as.numeric(`Blast count`)) %>%
  ungroup()
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0.
Please use `reframe()` instead.
When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly.`summarise()` has grouped output by 'orig.ident', 'PredictionRefined', 'Blast count', 'Cell number', 'Sample', 'Gender', 'Days from diagnosis'. You can override using the `.groups` argument.Adding missing grouping variables: `PredictionRefined`, `Cell number`
nrow(pred_malignant_vs_blast_count)
[1] 26
pred_malignant_vs_blast_count %>%
  ggplot(aes(
    x = PredictionRefinedProportion,
    y = `Blast count`,
    group = Sample,
    color = Sample
  )) +
  geom_point() +
  geom_abline(slope = 1, intercept = 0) +
  ggrepel::geom_text_repel(aes(label = orig.ident),
                           size = 3,
                           max.overlaps = Inf) +
  theme_bw() +
  coord_equal() +
  theme(panel.grid = element_blank())

cor.test(
  pred_malignant_vs_blast_count$PredictionRefinedProportion,
  pred_malignant_vs_blast_count$`Blast count`
)

    Pearson's product-moment correlation

data:  pred_malignant_vs_blast_count$PredictionRefinedProportion and pred_malignant_vs_blast_count$`Blast count`
t = 12.296, df = 24, p-value = 7.529e-12
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.8460991 0.9680065
sample estimates:
      cor 
0.9289867 

If the sex bias in healthy and AML samples matters, then the difference between predicted and observed blast count (error) should be larger in females than males.

Predicted proportion of malignant cells agrees less with clinical blast count for female samples. Markedly, female samples have an overestimated proportion of malignant cell.

However, this could also be due to other factors covarying with Sex, like mutated gene or blast count.

Median absolute error 10.8%(F), 3.1%(M).

pred_malignant_vs_blast_count_error <- pred_malignant_vs_blast_count %>%
  mutate(error = PredictionRefinedProportion - `Blast count`,
         absolute_error = abs(error),
         relative_error = PredictionRefinedProportion / `Blast count`) %>%
  pivot_longer(
    cols = c(error, absolute_error, relative_error),
    names_to = "error_type",
    values_to = "error"
  ) %>%
  group_by(error_type, Gender) 

pred_malignant_vs_blast_count_error %>%
  summarize(median_error = round(median(error), 3))
`summarise()` has grouped output by 'error_type'. You can override using the `.groups` argument.
symmetric_limits <- function (x)
{
  max <- max(abs(x))
  c(-max, max)
}

p1 <- pred_malignant_vs_blast_count_error %>%
  filter(error_type %in% c("error", "absolute_error")) %>%
  ggplot(aes(x = error_type, y = error * 100, fill = Gender)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_boxplot(outlier.size = 0) +
  geom_point(pch = 21, position = position_jitterdodge(jitter.width = 0.1)) +
  scale_y_continuous(labels = scales::percent_format(scale = 1)) +
  theme_bw() +
  theme(panel.grid = element_blank(), legend.position = "None") +
  ylab("Prediction error % blasts") +
  scale_y_continuous(limits = symmetric_limits) +
  xlab(element_blank()) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p2 <- pred_malignant_vs_blast_count_error %>%
  filter(error_type %in% c("relative_error")) %>%
  ggplot(aes(x = error_type, y = log2(error), fill = Gender)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_boxplot(outlier.size = 0) +
  geom_point(pch = 21, position = position_jitterdodge(jitter.width = 0.1)) +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  ylab("log2 relative error % blasts") +
  scale_y_continuous(limits = symmetric_limits) +
  xlab(element_blank()) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggpubr::ggarrange(p1, p2, align = "h", widths = c(1, 1.2))

Data quality

According to manuscript, cells were filtered for >1000 UMI, >500 genes and <20% mitochondrial+ribosomal transcripts.

The Rdata object contains filtered cells.

VlnPlot(galen_aml_donors, features = c("nCount_RNA", "nFeature_RNA"), ncol = 1, log = T, pt.size = 0)

Low dimensional embedding

AML and healthy donors

Normalize data by library size and log transform.
Identify 2000 highly variable genes.
Scale/z-score highly variable genes.

galen_aml_donors <- NormalizeData(galen_aml_donors, scale.factor = 10000)
galen_aml_donors <- FindVariableFeatures(galen_aml_donors)
galen_aml_donors <- ScaleData(galen_aml_donors)

Calculate PCA and check elbow plot for appropriate number of PCs for UMAP embedding.

galen_aml_donors <- RunPCA(galen_aml_donors)
ElbowPlot(galen_aml_donors, ndims = 50) +
  coord_cartesian(ylim = c(0, NA))

Calculate UMAP embedding.

galen_aml_donors <- RunUMAP(galen_aml_donors, dims = 1:15)

Check UMAP for technical and biological variation.

Higher read counts in erythrocyte progenitors.

# technical
FeaturePlot(galen_aml_donors, features = "nCount_RNA", pt.size = 0.005, max.cutoff = "q99") + coord_equal()

FeaturePlot(galen_aml_donors, features = "nFeature_RNA", pt.size = 0.005) + coord_equal()

# annotation biological
DimPlot(galen_aml_donors, group.by = "SampleClass", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(galen_aml_donors, group.by = "IsCancerCelltype", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(galen_aml_donors, group.by = "PredictionRefined", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(galen_aml_donors, group.by = "CellType", shuffle = T, pt.size = 0.005, label = T, label.size = 3) + coord_equal()

DimPlot(galen_aml_donors, group.by = "orig.ident", shuffle = T, pt.size = 0.005, label = T, label.size = 2) + coord_equal() + NoLegend()

FeaturePlot(galen_aml_donors, features = "Age", pt.size = 0.005) + coord_equal()

DimPlot(subset(galen_aml_donors, SampleClass == "HealthyDonor"), group.by = "orig.ident", shuffle = T, pt.size = 0.005) + coord_equal()

# sex differences
DimPlot(galen_aml_donors, group.by = "Gender", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(subset(galen_aml_donors, SampleClass == "AmlDonor"), group.by = "Gender", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(subset(galen_aml_donors, SampleClass == "AmlDonor"), group.by = "Sample", shuffle = T, pt.size = 0.005, split.by = "Gender") + coord_equal()

DimPlot(galen_aml_donors, group.by = "Sample", shuffle = T, pt.size = 0.005, split.by = "Gender") + coord_equal()

Embedding healthy donors

Create an embedding of just the healthy samples to visualize the normal hematopoietic trajectory.
Same procedure as above.

galen_aml_donors_healthy <- subset(galen_aml_donors, SampleClass == "HealthyDonor")
galen_aml_donors_healthy <- FindVariableFeatures(galen_aml_donors_healthy)
galen_aml_donors_healthy <- ScaleData(galen_aml_donors_healthy)
galen_aml_donors_healthy <- RunPCA(galen_aml_donors_healthy)
ElbowPlot(galen_aml_donors_healthy, ndims = 50) +
  coord_cartesian(ylim = c(0, NA))

galen_aml_donors_healthy <- RunUMAP(galen_aml_donors_healthy, dims = 1:15)
DimPlot(galen_aml_donors_healthy, group.by = "CellType", shuffle = T, pt.size = 0.01, label = T, label.size = 3) +
  coord_equal()

DimPlot(galen_aml_donors_healthy, group.by = "orig.ident", shuffle = T, pt.size = 0.01, label = T, label.size = 3) +
  coord_equal() + NoLegend()

AML and healthy donors excluding sex-specific genes

SDE scores for all protein-coding genes in 45 tissues common to men and women. Genes were analyzed by NOISeqBIO with scores of zero given for genes with insignificant differential expression. Other genes have SDE scores below zero for men-biased expression and above zero for women-biased expression. (CSV 2205 kb)

Bone marrow is not available. Instead use whole blood and spleen.

sex_genes <- read_csv("gershoni_et_al/12915_2017_352_MOESM3_ESM.csv")
Rows: 18759 Columns: 46── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): Gene
dbl (45): Adipose-Subcutaneous, Adipose-Visceral, Adrenal_Gland, Artery-Aorta, Artery-Coronary, Artery-Tibial, Bladder, Brain-Amygdala, Brain-Anterior_cingulate_cortex, Brain-Caudate, Brain-...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
sex_genes <- sex_genes %>% 
  select(Gene, Spleen, Whole_Blood) %>%
  filter(Spleen != 0 | Whole_Blood != 0) %>%
  mutate(gene = gsub("_.*", "", Gene)) %>%
  pull(gene)

sex_genes <- c(sex_genes, "XIST")

# make sure IDs match
sex_genes[!sex_genes %in% rownames(galen_aml_donors)]
character(0)
length(sex_genes)
[1] 28

Also exclude genes expressed on Y chromosome. 149 genes present in scRNA-seq data.

y_chrom_genes <- read_lines("reference_sources/gencode.v44_gene_names_chrY.txt")
y_chrom_genes <- y_chrom_genes[y_chrom_genes %in% rownames(galen_aml_donors)]
length(y_chrom_genes)
[1] 149
sex_genes <- unique(c(sex_genes, y_chrom_genes))

6 out of 2000 highly variable genes (AML+healthy) have sex-specific expression.

I don’t think removal of all genes that are differentially expressed betwen M and F should be remove, because there variables that co-vary with sex.

# sex-specific genes used for embedding
sex_genes[sex_genes %in% VariableFeatures(galen_aml_donors)]
[1] "EIF1AY" "MAP7D2" "VEGFA"  "XIST"   "SHOX"   "ASMT"  

Re-calculate PCA, excluding sex-specific genes from HVG list.

galen_aml_donors <-
  RunPCA(
    galen_aml_donors,
    features = VariableFeatures(galen_aml_donors)[!VariableFeatures(galen_aml_donors) %in% sex_genes],
    reduction.name = "pca_no_sex"
  )
ElbowPlot(galen_aml_donors, ndims = 50, reduction = "pca_no_sex") +
  coord_cartesian(ylim = c(0, NA))

galen_aml_donors <-
  RunUMAP(
    galen_aml_donors,
    dims = 1:15,
    reduction = "pca_no_sex",
    reduction.name = "umap_no_sex"
  )

I would check if the integration of sexes has improved if CalcAlignmentMetric was still available in Seurat v5…

MixingMetric has replaced CalcAlignmentMetric (Seurat v2).

Explanation of MixingMetric

  1. Look at the k.max nearest neighbors for a cell across all datasets.
  2. Compute the k closest neighbors for each dataset individually for the same cell.
  3. Find the rank in the overall neighborhood list (from 1.) that corresponds to the kth neighbor in each dataset (max of k.max) and take the median across all datasets.
  4. Compute 1-3 for every cell and average. This average is the value that is returned by the MixingMetric function. However, it’s usually more intuitive to think of high values of “mixing” to be better so for visualization, we often plot max.k - MixingMetric.

Deafult max.k = 300.

According to this metric, mixing of cells from male and female AML donors has slightly improved.

mixing_aml_sexes <- MixingMetric(
  subset(galen_aml_donors, SampleClass == "AmlDonor"),
  reduction = "pca",
  dims = 1:15,
  grouping.var = "Gender"
)
mixing_aml_sexes_corrected <- MixingMetric(
  subset(galen_aml_donors, SampleClass == "AmlDonor"),
  reduction = "pca_no_sex",
  dims = 1:15,
  grouping.var = "Gender"
)
300 - mean(mixing_aml_sexes)
[1] 253.2416
300 - mean(mixing_aml_sexes_corrected)
[1] 254.2387
# sex differences
DimPlot(
  subset(galen_aml_donors, SampleClass == "AmlDonor"),
  reduction = "umap_no_sex",
  group.by = "Gender",
  shuffle = T,
  pt.size = 0.005
) + coord_equal()

Task 2: ML classifier malignant cells

Task Description

An interesting feature of the dataset is the availability of labels distinguishing malignant and non-malignant single cells. The second task is to exploit these labels to build a suitable ML classifier that predicts the normal or malignant identity of single cells. Please detail on how your classifier works, whether it is interpretable, its performance, and the train-test split you use for your machine learning experiment.

Considerations for the task

The tricky part is not training a classifier, but to consider biology and technology to train a good classifier.

I assume that “labels distinguishing malignant and non-malignant single cells” in the task description refers to the genotyped cells, and not the labels predicted by van Galen et al. based on their random forest classifiers.

Van Galen et al. train two random forest classifiers (see below), in order to classify cells from AML donors into malignant and normal cells.
The first classifier is for HSPC cell types and the second for HSPC cell types + malignant vs. normal. Each classifier consists of an “outer” classifier for feature selection and an “inner” classifier that is used for prediction. Due to very limited number of genotyped cells that can be used for training, they do not hold out a test set. Instead, to evaluate their model performance, they perform 5-fold cross validation of the “inner” classifier.
Finally, they use the model trained on all available data for classification of AML cells that were not genotyped.

I assume that at the time there was no independent dataset available with genotyped AML cells to test for generalizability of the model to other scRNA-seq technologies (droplet-based, 5’ sequencing), human populations, or driver mutations.

Points of critique

  1. Including all training data in the feature selection makes the CV results of the “inner” classifier less informative.
  2. The CV is supposed to evaluate over- and underfitting. To estimate overfitting (complexity of the model), the model should be evaluated on how well it extrapolates to another donor and to another driver mutation, not a random sample of all training cells.
  3. All healthy cells in the training data are male. Van Galen do not address this by removing sex-specific genes from the features. (I’m honestly suprised that the classifier doesn’t perform even worse on female samples based on blast count than it does. A male sample with loss of y might play a role, and expression level/sparsity of sex-specific genes.)
  4. Cells were genotyped based on transcripts of following genes:
    TET2, SETD2, PTPN11, NRAS, SF3A1, BCOR, KIT, RAD21, TP53, BRCC3, RUNX1, FLT3, IDH2, DNMT3A, SMC3, NPM1, KRAS.
    Cells with higher expression of these genes are more likely to be genotyped and make it into the training data. Their expression should be excluded from the feature list, also to make it more generalisable to other driver mutations.
  5. The classifier does not consider possible differences in transcription between non-malignant HSPCs from AML donors and healthy donors. Non-malignant cells from AML donors have possibly altered transcription due to changes in the environment in the bone marrow.
    The classifier only considers healthy cells from healthy donors, and not non-malignant cells from AML donors. This is a limitation of the technology, because cells with wt transcript detected but no mutated transcript detected cannot confidently be classified as normal, due to clonality and heterozygosity.
    However, there might be a subset of cells from donors with known zygosity and clonal structure where this is possible.

What I agree with, is the observation that, generally, malignant HSCs are transcriptionally more similar to healthy HSCs than to malignant monocyte progenitors. Therefore, it makes sense to guide the model with information on similarity towards healthy HSPC cell types.

Random forest classifiers allow the extraction of feature importance metrics (e.g. number of trees in which a feature is used). However, one has to differ between a minimal optimal subset and an all relevant subset of genes. This is especially relevant for gene-expression based classifiers due to redundance (think gene modules/signatures). See Kursa et al. 2014 for more details.

Van Galen et al. select as features the genes chosen most often in the outer classifier, across 1000 trees. This does not seem to consider classes.

Ideas

  • Instead of predicting HSC and HSC-like cells, why not give a similarity score towards each healthy cell type as input feature. This would allow to inform the classifier for relevant features like combinations of signatures (e.g. LSCs arising from GMPs with mixed HSC/GMP signature).
  • Why include lymphoid and erythroid cell types in the malignant vs. non-malignant classifier? They are transcriptionally very distinct and in AML they are not malignant. The classifier might have an easier time if they are excluded.
  • Consider other methods to assign the most similar healthy cell type, such as label transfer via projection onto the healthy reference (Azimuth).
  • Use an independent test set of genotypes AML cells.

Van Galen classifier malignant vs. normal AML cells

Transcriptional heterogeneity of malignant and healthy cells

The goal is to develop a classifier of malignant vs. healthy HSPCs and myeloid cells from AML patients, that is generalizable to other AML types with other driver mutations and other scRNA-seq technologies/datasets.

Evaluate the transcriptional heterogeneity of AML subtypes and malignant vs. healthy cells.

Q1: Do healthy cells differ from malignant cells based on their transcriptome?

Q2: Is the malignant transcriptional profile specific to driver mutations?

We are only interested in myeloid and HSPC cells here, because distinction of erythroid and lymphoid cells is relatively easy. Those cells are not malignant in acute myeloid leukemia.

For a start, use HSPCs + myeloid cells, according to van Galen cell type annotation.

Include healthy donors, to see if the cells genotyped as wt group with definitive healthy cells.

selected_samples <- donor_metadata %>%
  # filter(
  #   !grepl("BM", Sample),
  #   # `Blast count` > 0.1,
  #   # `Days from diagnosis` == "D0"
  #   ) %>%
  pull(SampleId)

galen_aml_donors_myeloid_hspc <-
  subset(
    galen_aml_donors,
    subset = (orig.ident %in% selected_samples & SampleClass == "HealthyDonor" | 
      ((CellTypeGeneral == "Myeloid/HSPC-like" |
      CellTypeGeneral == "Myeloid/HSPC") & genotype != "not_detected"))
  )
table(galen_aml_donors_myeloid_hspc$genotype)

     healthy    malignant not_detected 
        1734          886         6558 
# only keep SampleId-genotype combinations with at least 10 cells
samples_enough_cells <- names(which(table(galen_aml_donors_myeloid_hspc$orig.ident) >= 10))
galen_aml_donors_myeloid_hspc <-
  subset(
    galen_aml_donors_myeloid_hspc,
    subset = orig.ident %in% samples_enough_cells
  )
galen_aml_donors_myeloid_hspc@meta.data <- galen_aml_donors_myeloid_hspc@meta.data %>%
  mutate(
    SampleId_genotype =
      paste(
        galen_aml_donors_myeloid_hspc$orig.ident,
        galen_aml_donors_myeloid_hspc$genotype,
        sep = "_"
      )
  ) %>%
  mutate(SampleId_genotype = ifelse(SampleClass == "HealthyDonor", gsub("not_detected", "healthy", SampleId_genotype), SampleId_genotype))
pb_counts <-
  Seurat::AggregateExpression(
    galen_aml_donors_myeloid_hspc,
    assays = "RNA",
    slot = "counts",
    # group.by = "SampleId_CellTypeGeneral"
    group.by = "SampleId_genotype"
    # group.by = "orig.ident"
  )$RNA
Names of identity class contain underscores ('_'), replacing with dashes ('-')
group_anno <- colnames(pb_counts)
group_anno <- gsub(".*-", "", group_anno)
 
pb_dge <- edgeR::DGEList(
    counts = pb_counts,
    samples = colnames(pb_counts),
    group = group_anno
)

Filter out samples with low read count (=few filtered cells).
Keep samples with read counts > 50,000, which is all samples.

summary(pb_dge$samples$lib.size)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
    3670    47074   195297   923075   618775 15170692 
keep.samples <- pb_dge$samples$lib.size > 5e4
table(keep.samples)
keep.samples
FALSE  TRUE 
   10    27 
# pb_dge <- pb_dge[, keep.samples]

Filter out lowly expressed genes.
Keep genes with at least 10 CPM in at least 2 samples.

# genes with at least 1 CPM in at least 2 samples
keep.genes <- rowSums(edgeR::cpm(pb_dge) >=10) >= 2
table(keep.genes)
keep.genes
FALSE  TRUE 
15257 12642 
 
pb_dge <- pb_dge[keep.genes, , keep=FALSE]

TMM normalization is performed to estimate effective library size.

pb_dge <- edgeR::calcNormFactors(pb_dge, method = "TMM")

Calculate Multidimensional scaling (MDS) plot of distances between gene expression profiles.

pb_mds <- limma::plotMDS(pb_dge, plot = F)

pb_mds_df <- data.frame(
  list(
    x = pb_mds$x,
    y = pb_mds$y,
    lib.size = pb_dge$samples$lib.size,
    sample = gsub("-.*", "", colnames(pb_dge)),
    group = pb_dge$samples$group
  )
) %>%
  merge(donor_metadata, by.x = "sample", by.y = "SampleId", all.x = T)

Check that major source of variation is not technical (sample size) or irrelevant biological information in this case (blast count).

xlab_text <- paste0(
    "Leading logFC dim 1 (",
    round(pb_mds$var.explained[1] * 100, 1),
    "%)"
  )
ylab_text <- paste0(
    "Leading logFC dim 2 (",
    round(pb_mds$var.explained[2] * 100, 1),
    "%)"
  )

mds_theme <- theme_bw() +
  theme(panel.grid = element_blank())

pb_mds_df %>%
  unique() %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = log(lib.size))) +
  # ggrepel::geom_text_repel() +
  xlab(xlab_text) + ylab(ylab_text) +
  mds_theme


pb_mds_df %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = `Blast count`)) +
  # ggrepel::geom_text_repel() +
  xlab(xlab_text) + ylab(ylab_text) +
  mds_theme


pb_mds_df %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = `Days from diagnosis` == "D0")) +
  # ggrepel::geom_text_repel() +
  xlab(xlab_text) + ylab(ylab_text) +
  mds_theme


pb_mds_df %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = group)) +
  # ggrepel::geom_text_repel() +
  xlab(xlab_text) + ylab(ylab_text) +
  mds_theme


pb_mds_df %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = Gender)) +
  # ggrepel::geom_text_repel() +
  xlab(xlab_text) + ylab(ylab_text) +
  mds_theme


pb_mds_df %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = Sample)) +
  ggrepel::geom_text_repel(
    size = 2,
    min.segment.length = 0,
    max.overlaps = Inf
  ) +
  xlab(xlab_text) + ylab(ylab_text) +
  mds_theme +
  theme(legend.position = "None")

Check if the samples split by mutation.

sampleId_mutation_list <- donor_metadata %>%
  select(SampleId, `RHP Mutations`, `Common translocation`) %>%
  filter(SampleId %in% pb_mds_df$sample) %>%
  pivot_longer(cols = c(`RHP Mutations`, `Common translocation`), values_to = "driver_mutation") %>%
  select(-name) %>%
  filter(!driver_mutation %in% c("Not performed", "None Detected", "Unknown", "NA") & !is.na(driver_mutation)) %>%
  separate_longer_delim(cols = driver_mutation, "/// ") %>%
  mutate(driver_mutation = gsub(" .*", "", driver_mutation)) %>%
  # unique for samples with multiple mutations in same gene
  unique() %>%
  mutate(value = T) %>%
  pivot_wider(id_cols = SampleId, names_from = driver_mutation, values_fill = F) %>%
  pivot_longer(cols = -SampleId, names_to = "driver_mutation")
pb_mds_df_driver_mutation <- merge(pb_mds_df, sampleId_mutation_list, by.x = "sample", by.y = "SampleId", all.x = T)

PC1 aligns with DNMTA3. Indication that DNMT3 has a distinct transcriptional profile.

“DNMT3A mutations occur in approximately 20% of AML cases and are associated with changes in DNA methylation. CDKN2B plays an important role in the regulation of hematopoietic progenitor cells and DNMT3A mutation is associated with CDKN2B promoter methylation.” https://pmc.ncbi.nlm.nih.gov/articles/PMC7106122/

KRAS, FLT3-ITD, NPM1, NRAS

pb_mds_df_driver_mutation %>%
  filter(group == "malignant") %>%
  ggplot(aes(x, y, label = sample)) +
  geom_point(aes(color = value)) +
  facet_wrap(~driver_mutation) +
  xlab(xlab_text) + ylab(ylab_text) +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  coord_equal()

unique(sampleId_mutation_list$driver_mutation)
 [1] "KRAS"          "NRAS"          "NOTCH2"        "SF3A1"         "CBFB-MYH11"    "DNMT3A"        "NPM1"          "TET2"          "FLT3-ITD"     
[10] "CEBPA"         "FLT3"          "JAK3"          "TP53"          "RUNX1"         "SETD2"         "BCOR"          "BCORL1"        "NOTCH1"       
[19] "SMC3"          "ATM"           "BRCC3"         "KIT"           "RAD21"         "RUNX1-RUNX1T1"
genes_genotypes <-
  c(
    "NRAS",
    "KRAS",
    "SF3A1",
    "NOTCH1",
    "NOTCH2",
    "SF3A1",
    "CBFB",
    "MYH11",
    "NPM1",
    "JAK3",
    "DNMT3A",
    "FLT3",
    "TP53",
    "SETD2",
    "RUNX1",
    "BCOR",
    "BCORL1",
    "PTPN11",
    "SMC3",
    "IDH2",
    "TET2",
    "BRCC3",
    "KIT",
    "RAD21",
    "MLL",
    "CEBPA"
  )

Azimuth projection

Alternative to cell type identification with a random forest model is label transfer from a reference atlas.

Perform label transfer from the Azimuth bone marrow reference atlas. Instead of training a random forest classifier to determine the most similar healthy equivalent of a cell.

Azimuth provides cell type labels at 2 levels, see interactive reference.

For this step, we are not interested in the genes distinguishing the different cell types because they are already well defined. So we don’t need a RF classifier where we can extract feature importance metrics.

The label transfer is very fast and easy and provides a well established reference framework of cell type classifications and hierarchies.

Reference downloaded from here.

library(Azimuth)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
Registered S3 method overwritten by 'SeuratDisk':
  method            from  
  as.sparse.H5Group Seurat

Attaching shinyBS
library(SeuratData)
# The RunAzimuth function can take a Seurat object as input
# overwrites miscellanous data, so saving as new object
galen_aml_donors_azimuth <- RunAzimuth(galen_aml_donors,
                                       reference = "azimuth_reference/bonemarrowref.SeuratData/inst/azimuth/")

Add Azimuth labels to original seurat object.

galen_aml_donors <-
  AddMetaData(
    galen_aml_donors,
    galen_aml_donors_azimuth@meta.data,
    col.name = c("predicted.celltype.l1", "predicted.celltype.l2")
  )

The function returns cell type labels on 2 levels, and 2 QC metrics, a mapping score and a prediction score for each level.
It also returns the projection of the cells onto the reference UMAP space.

DimPlot(galen_aml_donors_azimuth, group.by = "predicted.celltype.l1", shuffle = T, label = T) + coord_equal()
FeaturePlot(galen_aml_donors_azimuth, features = "predicted.celltype.l1.score") + coord_equal()
DimPlot(galen_aml_donors_azimuth, group.by = "predicted.celltype.l2", shuffle = T, label = T) + coord_equal()
FeaturePlot(galen_aml_donors_azimuth, features = "predicted.celltype.l2.score") + coord_equal()
FeaturePlot(galen_aml_donors_azimuth, features = "mapping.score") + coord_equal()

DimPlot(galen_aml_donors_azimuth, group.by = "SampleClass", shuffle = T) + coord_equal()
DimPlot(galen_aml_donors_azimuth, group.by = "CellType", shuffle = T, label = T) + coord_equal()

Compare Azimuth annotations with backspin cluster annotation, followed by RF. Also compare with malignant/non malignant RF annotation.

heatmap(table(galen_aml_donors$backspin_celltype, galen_aml_donors$predicted.celltype.l1), scale = NULL, Rowv = NA, Colv = NA)
heatmap(table(galen_aml_donors$backspin_celltype, galen_aml_donors$predicted.celltype.l2), scale = NULL, Rowv = NA, Colv = NA)

heatmap(table(galen_aml_donors$CellType, galen_aml_donors$predicted.celltype.l1), scale = NULL, Rowv = NA, Colv = NA)
heatmap(table(galen_aml_donors$CellType, galen_aml_donors$predicted.celltype.l2), scale = NULL, Rowv = NA, Colv = NA)

In the end, I realized that the Azimuth annotations are problematic, becaue either very granular or useless because lumping everything into HSPC.

Train classifier

backspin celltype annotation is available for 783 cells of 1,590 BM5 CD34+CD38–.

y <- factor(galen_aml_donors_healthy$backspin_celltype)
X <- galen_aml_donors_healthy@assays$RNA@layers$data
X <- X[,!is.na(y)]
rownames(X) <- rownames(galen_aml_donors_healthy)
y <- y[!is.na(y)]

# # combine HSC and Prog
# y[y %in% c("HSC", "Prog")] <- "HSC_Prog_"

Van Galen et al. select genes with average normalized expression >= 0.01 (normalized to 10k reads). This is biased by class. Instead, select genes expressed in 5% cells in at least 1 class (10141 genes).

# mean normalized expression >= 0.01
keep.genes <- rowMeans(exp(X) - 1) >= 0.01
table(keep.genes)
keep.genes
FALSE  TRUE 
13825 14074 
# expressed in 1% cells
keep.genes.expressed <- rowSums((X > 0) / ncol(galen_aml_donors_healthy)) >= 0.01
table(keep.genes.expressed)
keep.genes.expressed
FALSE  TRUE 
16740 11159 
# expressed in 1% cells in at least 1 class
classes <- unique(y)
keep.genes.class <- lapply(classes, FUN = function(class) {
  genes <- names(which(rowSums((X[,y == class] > 0) / sum(y == class)) >= 0.05))
  return(genes)
})
keep.genes.class <- rownames(X) %in% unlist(keep.genes.class)
table(keep.genes.class)
keep.genes.class
FALSE  TRUE 
17758 10141 
X <- X[which(keep.genes.class),]

All cells are male. Remove sex-specific genes anyways.

keep.genes <- !rownames(X) %in% sex_genes
table(keep.genes)
keep.genes
FALSE  TRUE 
   38 10103 
X <- X[which(keep.genes),]

sampsize is the number of cells sampled from each class. By default ceiling(.632*nrow(x)). This is one way to deal with imbalanced data in RF by using balanced data sets for each tree, even if the data is not balanced, which is made possible by bootstrap.

Train 100 trees because I’m impatient.

set.seed(123)
rf.celltype.outer <- randomForest(
  x = t(X),
  y = y,
  sampsize = rep(50, length(unique(y))),
  ntree = 200,
  do.trace = F
)
rf.celltype.outer

Call:
 randomForest(x = t(X), y = y, ntree = 200, sampsize = rep(50,      length(unique(y))), do.trace = F) 
               Type of random forest: classification
                     Number of trees: 200
No. of variables tried at each split: 100

        OOB estimate of  error rate: 16.51%
Confusion matrix:
           B cDC CTL earlyEry GMP  HSC lateEry Mono  NK pDC Plasma ProB Prog ProMono   T class.error
B        109   0   1        0   0    0       0    2   0   0      0    0    0       0   1  0.03539823
cDC        2 240   2        1   2    1       0   16   2   6      0    0    0       9   0  0.14590747
CTL        1   0 199        1   0    0       0    0  12   0      0    1    0       0  47  0.23754789
earlyEry   0   0   1      407   6   29      94    0   0   0      1   11   67       1   2  0.34248788
GMP        1   4   2        2 455    6       0    1   0   0      1    2   38      19   1  0.14473684
HSC        2   0   4        3   1 1143       0    0   0   0      0    9  107       0   1  0.10000000
lateEry    0   0   0        2   0    1     257    0   0   0      0    0    1       1   0  0.01908397
Mono       1   6   1        0   0    0       2  540   0   0      0    0    0      17   0  0.04761905
NK         0   0   6        0   0    0       0    1 147   0      0    0    1       0   2  0.06369427
pDC        1   0   0        0   0    0       0    0   0  82      0    1    7       0   0  0.09890110
Plasma     0   0   0        0   0    0       0    0   0   1     68    0    0       0   0  0.01449275
ProB       2   0   0        0   0    2       0    0   0   1      0  184    3       0   1  0.04663212
Prog       0   0   0       15  68  307       0    0   1   1      0   12  751       0   1  0.35034602
ProMono    0   4   2        1  21    0       1   78   0   0      0    0    0     516   1  0.17307692
T          1   1  35        1   0    0       0    1   5   1      0    0    0       0 675  0.06250000

Select 1k features from 14074 genes.

bestvar is the variable used to split the node (0 if node is terminal).

rf.celltype.outer.used1k <-
  names(sort(table(
    rownames(rf.celltype.outer$importance)[rf.celltype.outer$forest$bestvar[rf.celltype.outer$forest$bestvar !=
                                                                              0]]
  ), decreasing = TRUE)[1:1000])

plot(rf.celltype.outer$importance[rf.celltype.outer.used1k, 1])  # correlates to importance measure

set.seed(123)
rf.celltype.inner <- randomForest(
  x = t(X[rf.celltype.outer.used1k,]),
  y = y,
  sampsize = rep(50, length(unique(y))),
  ntree = 200,
  do.trace = F
)
rf.celltype.inner

Call:
 randomForest(x = t(X[rf.celltype.outer.used1k, ]), y = y, ntree = 200,      sampsize = rep(50, length(unique(y))), do.trace = F) 
               Type of random forest: classification
                     Number of trees: 200
No. of variables tried at each split: 31

        OOB estimate of  error rate: 13.49%
Confusion matrix:
           B cDC CTL earlyEry GMP  HSC lateEry Mono  NK pDC Plasma ProB Prog ProMono   T class.error
B        106   0   1        0   0    0       0    2   0   0      0    0    0       0   4  0.06194690
cDC        3 249   0        1   2    1       0    3   0   6      0    1    2      13   0  0.11387900
CTL        1   0 220        0   0    0       0    0  12   0      1    0    0       0  27  0.15708812
earlyEry   2   1   2      435  13   18      71    0   0   0      1   11   63       0   2  0.29725363
GMP        1   2   1        3 456    4       0    0   0   3      1    1   32      27   1  0.14285714
HSC        1   0   0        7   2 1142       0    0   0   1      0   16  100       0   1  0.10078740
lateEry    0   0   0        7   0    0     255    0   0   0      0    0    0       0   0  0.02671756
Mono       1   9   0        0   0    0       2  542   0   0      0    0    0      13   0  0.04409171
NK         0   0   2        0   0    0       0    0 154   0      0    0    0       0   1  0.01910828
pDC        0   1   0        0   0    1       0    0   0  86      0    1    2       0   0  0.05494505
Plasma     0   0   0        0   0    0       0    0   0   1     68    0    0       0   0  0.01449275
ProB       0   0   0        0   0    3       0    0   0   1      0  188    0       0   1  0.02590674
Prog       0   0   0        8  47  206       0    0   2   3      0   14  876       0   0  0.24221453
ProMono    1   5   1        3  16    0       0   71   0   0      1    0    0     525   1  0.15865385
T          1   1  33        0   0    0       0    0   4   1      0    0    0       0 680  0.05555556
y_pred_healthy <- predict(rf.celltype.inner, newdata = t(X), type = "prob")
rf.celltype.inner$confusion
           B cDC CTL earlyEry GMP  HSC lateEry Mono  NK pDC Plasma ProB Prog ProMono   T class.error
B        106   0   1        0   0    0       0    2   0   0      0    0    0       0   4  0.06194690
cDC        3 249   0        1   2    1       0    3   0   6      0    1    2      13   0  0.11387900
CTL        1   0 220        0   0    0       0    0  12   0      1    0    0       0  27  0.15708812
earlyEry   2   1   2      435  13   18      71    0   0   0      1   11   63       0   2  0.29725363
GMP        1   2   1        3 456    4       0    0   0   3      1    1   32      27   1  0.14285714
HSC        1   0   0        7   2 1142       0    0   0   1      0   16  100       0   1  0.10078740
lateEry    0   0   0        7   0    0     255    0   0   0      0    0    0       0   0  0.02671756
Mono       1   9   0        0   0    0       2  542   0   0      0    0    0      13   0  0.04409171
NK         0   0   2        0   0    0       0    0 154   0      0    0    0       0   1  0.01910828
pDC        0   1   0        0   0    1       0    0   0  86      0    1    2       0   0  0.05494505
Plasma     0   0   0        0   0    0       0    0   0   1     68    0    0       0   0  0.01449275
ProB       0   0   0        0   0    3       0    0   0   1      0  188    0       0   1  0.02590674
Prog       0   0   0        8  47  206       0    0   2   3      0   14  876       0   0  0.24221453
ProMono    1   5   1        3  16    0       0   71   0   0      1    0    0     525   1  0.15865385
T          1   1  33        0   0    0       0    0   4   1      0    0    0       0 680  0.05555556

earlyEry misclassified as lateEry and Prog. Prog misclassified as HSC.

heatmap(rf.celltype.inner$confusion, Rowv = NA, Colv = NA, scale = NULL)


rf.celltype.inner$confusion %>%
  as.data.frame() %>%
  rownames_to_column("class") %>%
  ggplot(aes(x = class, y = class.error)) +
  geom_bar(stat = "identity")

Predict celltype of malignant AML cells.

“In four patients (AML314, AML371, AML722B and AML997), for which we detected few mutant transcripts and few high quality cells, we could not confidently assign malignant cells.”

I also exclude AML475 and AML420B because they also only have 7 and 6 cells genotyped as malignant (and upon first classification, those cells were lymphoid cells).

galen_aml_donors_malignant <- subset(galen_aml_donors, subset = (genotype == "malignant" & SampleClass != "HealthyDonor"))
table(galen_aml_donors_malignant$Sample)

AML1012 AML210A  AML328  AML329  AML371 AML419A AML420B  AML475  AML556 AML707B AML722B  AML916 AML921A  AML997 
     15      45      34      92       6     146       6       7     377      40       1      21     143       6 
selected_samples <-
  donor_metadata$Sample[!donor_metadata$Sample %in% c("AML314", "AML371", "AML722B", "AML997", "AML475", "AML420B")]
galen_aml_donors_malignant <-
  subset(galen_aml_donors_malignant, Sample %in% selected_samples)
X_malignant <- galen_aml_donors_malignant@assays$RNA@layers$data
rownames(X_malignant) <- rownames(galen_aml_donors_malignant)
X_malignant <- X_malignant[rf.celltype.outer.used1k,]

y_pred_malignant <- predict(rf.celltype.inner, newdata = t(X_malignant), type = "prob")

y_pred_max <- colnames(y_pred_malignant)[max.col(y_pred_malignant)]

There are cells classified as lymphocytes, despite detection of mutated transcripts. Same for early erythrocytes, pDC.

table(y_pred_max)
y_pred_max
       B      cDC      CTL earlyEry      GMP      HSC  lateEry     Mono       NK      pDC   Plasma     ProB     Prog  ProMono        T 
      11       85        7       31      112        4        5      155        8        7        2       18      176      285        7 
table(y_pred_max, galen_aml_donors_malignant$Sample)
          
y_pred_max AML1012 AML210A AML328 AML329 AML419A AML556 AML707B AML916 AML921A
  B              0       0      0      2       2      0       1      0       6
  cDC            1       8      1      5       9      1       0      1      59
  CTL            0       0      0      0       0      5       0      1       1
  earlyEry       2       5     10      3       7      0       2      2       0
  GMP            4       4      1     51       8      3      21      0      20
  HSC            0       0      2      0       0      0       1      0       1
  lateEry        0       0      0      3       0      1       0      0       1
  Mono           1       8      0      0      40    103       0      0       3
  NK             0       0      2      0       0      2       1      0       3
  pDC            0       0      0      1       1      0       0      0       5
  Plasma         0       0      0      0       0      2       0      0       0
  ProB           0       1      4      0       7      0       1      5       0
  Prog           5       9     13     20      67      1      13     11      37
  ProMono        2      10      0      6       4    257       0      0       6
  T              0       0      1      1       1      2       0      1       1
t(round(t(
  table(y_pred_max, galen_aml_donors_malignant$Sample)
) / as.vector(colSums(
  table(y_pred_max, galen_aml_donors_malignant$Sample)
)), 2))
          
y_pred_max AML1012 AML210A AML328 AML329 AML419A AML556 AML707B AML916 AML921A
  B           0.00    0.00   0.00   0.02    0.01   0.00    0.03   0.00    0.04
  cDC         0.07    0.18   0.03   0.05    0.06   0.00    0.00   0.05    0.41
  CTL         0.00    0.00   0.00   0.00    0.00   0.01    0.00   0.05    0.01
  earlyEry    0.13    0.11   0.29   0.03    0.05   0.00    0.05   0.10    0.00
  GMP         0.27    0.09   0.03   0.55    0.05   0.01    0.52   0.00    0.14
  HSC         0.00    0.00   0.06   0.00    0.00   0.00    0.03   0.00    0.01
  lateEry     0.00    0.00   0.00   0.03    0.00   0.00    0.00   0.00    0.01
  Mono        0.07    0.18   0.00   0.00    0.27   0.27    0.00   0.00    0.02
  NK          0.00    0.00   0.06   0.00    0.00   0.01    0.03   0.00    0.02
  pDC         0.00    0.00   0.00   0.01    0.01   0.00    0.00   0.00    0.03
  Plasma      0.00    0.00   0.00   0.00    0.00   0.01    0.00   0.00    0.00
  ProB        0.00    0.02   0.12   0.00    0.05   0.00    0.03   0.24    0.00
  Prog        0.33    0.20   0.38   0.22    0.46   0.00    0.32   0.52    0.26
  ProMono     0.13    0.22   0.00   0.07    0.03   0.68    0.00   0.00    0.04
  T           0.00    0.00   0.03   0.01    0.01   0.01    0.00   0.05    0.01

There are some rather unexpected HSPC cell type predictions: early erythroid cells and pDC cells. While lymphoid cell types are pretty surely a misclassification, early ery and pDC can actually be mutated in AML, according to literature.

https://www.mdpi.com/2072-6694/14/14/3375 Acute myeloid leukemia (AML) with ≥2% plasmacytoid dendritic cells (pDC) has been recently described as AML with pDC differentiation (pDC-AML) characterized by pDC expansion with frequent RUNX1 mutations.

AML921A has 8% cells predicted as pDC and RUNX1 NM_001754 c.167T>C p.L56S (63.5%, VUS)

In combination with the possible signature combinations in AML cells, I want to keep those predictions.

However, there are too few cells to use them as classes in a classifier. Instead, I’ll include them as features, alongside genes, and predict malignant vs. normal classes.

I’ll only train 1 classifier, instead of inner and outer. This way, validation of correct prediction of malignant cells is more truthful.

X_healthy <- galen_aml_donors_healthy@assays$RNA@layers$data
X_healthy <- X_healthy[,!is.na(galen_aml_donors_healthy$backspin_celltype)]
rownames(X_healthy) <- rownames(galen_aml_donors_healthy)
# combine celltype prediction with gene expression
X_healthy_celltype <- rbind(X_healthy, t(y_pred_healthy))

X_malignant <- galen_aml_donors_malignant@assays$RNA@layers$data
rownames(X_malignant) <- rownames(galen_aml_donors_malignant)
# combine celltype prediction with gene expression
X_malignant_celltype <- rbind(X_malignant, t(y_pred_malignant))

X_celltype <- cbind(X_healthy_celltype, X_malignant_celltype)
dim(X_celltype)
[1] 27914  7828
X <- cbind(X_healthy, X_malignant)
dim(X)
[1] 27899  7828
y = factor(c(rep("healthy", ncol(X_healthy)), rep("malignant", ncol(X_malignant))))
table(y)
y
  healthy malignant 
     6915       913 

Keep cells expressed in >= 1% in at least one of the classes.

keep.genes.expressed <- rowSums((X > 0) / ncol(X)) >= 0.01
table(keep.genes.expressed)
keep.genes.expressed
FALSE  TRUE 
16569 11330 
# expressed in 1% cells in at least 1 class
classes <- unique(y)
keep.genes.class <- lapply(classes, FUN = function(class) {
  genes <- names(which(rowSums((X[,y == class] > 0) / sum(y == class)) >= 0.01))
  return(genes)
})
keep.genes.class <- rownames(X) %in% unlist(keep.genes.class)
names(keep.genes.class) <- rownames(X)
table(keep.genes.class)
keep.genes.class
FALSE  TRUE 
15572 12327 
X <- X[names(which(keep.genes.class)),]
X_celltype <- X_celltype[c(names(which(keep.genes.class)), colnames(y_pred_malignant)), ]
X_celltype_driver <- X_celltype[!rownames(X_celltype) %in% genes_genotypes, ]

Train 1 classifier with celltype probabilities as features and one without and one without genes with driver mutations.

set.seed(123)
rf.malignant <- randomForest(
  x = t(X),
  y = y,
  sampsize = rep(200, length(unique(y))),
  ntree = 200,
  do.trace = F
)
set.seed(123)
rf.malignant.celltype <- randomForest(
  x = t(X_celltype),
  y = y,
  sampsize = rep(200, length(unique(y))),
  ntree = 200,
  do.trace = F
)
set.seed(123)
rf.malignant.celltype.driver <- randomForest(
  x = t(X_celltype_driver),
  y = y,
  sampsize = rep(200, length(unique(y))),
  ntree = 200,
  do.trace = F
)

Including cell type probabilities gives a

rf.malignant

Call:
 randomForest(x = t(X), y = y, ntree = 200, sampsize = rep(200,      length(unique(y))), do.trace = F) 
               Type of random forest: classification
                     Number of trees: 200
No. of variables tried at each split: 111

        OOB estimate of  error rate: 8.23%
Confusion matrix:
          healthy malignant class.error
healthy      6385       530  0.07664497
malignant     114       799  0.12486309
rf.malignant.celltype

Call:
 randomForest(x = t(X_celltype), y = y, ntree = 200, sampsize = rep(200,      length(unique(y))), do.trace = F) 
               Type of random forest: classification
                     Number of trees: 200
No. of variables tried at each split: 111

        OOB estimate of  error rate: 7.78%
Confusion matrix:
          healthy malignant class.error
healthy      6419       496  0.07172813
malignant     113       800  0.12376780
rf.malignant.celltype.driver

Call:
 randomForest(x = t(X_celltype_driver), y = y, ntree = 200, sampsize = rep(200,      length(unique(y))), do.trace = F) 
               Type of random forest: classification
                     Number of trees: 200
No. of variables tried at each split: 110

        OOB estimate of  error rate: 8.01%
Confusion matrix:
          healthy malignant class.error
healthy      6383       532   0.0769342
malignant      95       818   0.1040526
y_malignant_celltype_pred <- predict(rf.malignant.celltype, newdata = t(X_celltype), type = "prob")
y_malignant_celltype_pred_max <- colnames(y_malignant_celltype_pred)[max.col(y_malignant_celltype_pred)]

donor_list <- unique(galen_aml_donors_malignant$Sample)
names(donor_list) <- donor_list

# donor_accuracy_rf.malignant.celltype <- lapply(donor_list, function(donor) sum(y_malignant_celltype_pred_max[donors == donor] == "malignant") / sum(donors == donor))
# donor_accuracy_rf.malignant.celltype

Model evaluation

Cross-validation on AML donors

Pick 4 donors at random (to save time).

Accuracy is how many cells are correctly classified as malignant.

With this cross validation, we could do more paramter testing. Like the number of features to pick for the inner cell type classifier. Or how many paramters to sample for each decision in the tree. Or the depth of the trees.

donors <- c(galen_aml_donors_healthy$Sample, galen_aml_donors_malignant$Sample)
table(donors)
donors
       AML1012        AML210A         AML328         AML329        AML419A         AML556        AML707B         AML916        AML921A            BM1 
            15             45             34             92            146            377             40             21            143            108 
           BM2            BM3            BM4      BM5 CD34+ BM5 CD34+CD38- 
           188            643           3738           1431            807 
donors_cv <- unique(galen_aml_donors_malignant$Sample, galen_aml_donors_healthy$Sample)
set.seed(123)
donors_cv <- sample(donors_cv, size = 4)
donors_cv
[1] "AML419A" "AML329"  "AML707B" "AML210A"
cv_results <- lapply(donors_cv, function(donor) {
  cat(donor)
  cat("\n")
  
  # train and validation split
  X_celltype_cv_train <- X_celltype[, donors != donor]
  X_celltype_cv_test <- X_celltype[, donors == donor]
  y_cv_train <- y[donors != donor]
  y_cv_test <- y[donors == donor]
  
  # train on test data
  set.seed(123)
  rf.malignant.celltype.cv <- randomForest(
    x = t(X_celltype_cv_train),
    y = y_cv_train,
    sampsize = rep(200, length(unique(y_cv_train))),
    ntree = 200,
    do.trace = FALSE
  )
  
  # predict validation data
  y_cv_pred <-
    predict(
      rf.malignant.celltype.cv,
      newdata = t(X_celltype_cv_test),
      type = "prob"
    )
  y_cv_pred_max <- colnames(y_cv_pred)[max.col(y_cv_pred)]
  
  # calculate accuracy
  accuracy <- sum(y_cv_pred_max == y_cv_test) / length(y_cv_pred_max)
  return(accuracy)
})
AML419A
AML329
AML707B
AML210A
names(cv_results) <- donors_cv

Cross validation when leaving out driver mutation genes.

cv_results_driver <- lapply(donors_cv, function(donor) {
  cat(donor)
  cat("\n")
  
  # train and validation split
  X_celltype_cv_train <- X_celltype_driver[, donors != donor]
  X_celltype_cv_test <- X_celltype_driver[, donors == donor]
  y_cv_train <- y[donors != donor]
  y_cv_test <- y[donors == donor]
  
  # train on test data
  set.seed(123)
  rf.malignant.celltype.cv <- randomForest(
    x = t(X_celltype_cv_train),
    y = y_cv_train,
    sampsize = rep(200, length(unique(y_cv_train))),
    ntree = 200,
    do.trace = FALSE
  )
  
  # predict validation data
  y_cv_pred <-
    predict(
      rf.malignant.celltype.cv,
      newdata = t(X_celltype_cv_test),
      type = "prob"
    )
  y_cv_pred_max <- colnames(y_cv_pred)[max.col(y_cv_pred)]
  
  # calculate accuracy
  accuracy <- sum(y_cv_pred_max == y_cv_test) / length(y_cv_pred_max)
  return(accuracy)
})
AML419A
AML329
AML707B
AML210A
names(cv_results_driver) <- donors_cv
donor_metadata %>%
  filter(Sample %in% donors_cv, `Days from diagnosis` == "D0") %>%
  select(Sample, `RHP Mutations`, `Common translocation`) %>%
  unique()
lapply(cv_results, round, 3)
$AML419A
[1] 0.842

$AML329
[1] 0.337

$AML707B
[1] 0.45

$AML210A
[1] 0.711
lapply(cv_results_driver, round, 3)
$AML419A
[1] 0.87

$AML329
[1] 0.391

$AML707B
[1] 0.225

$AML210A
[1] 0.778

Excluding genes with driver mutations increases patient CV accuracy slightly in 3/4 cases.

Cross-validation on driver mutations

TODO

To test the generalizability of the model on unseen mutations, cross validation should be performed with holding out all samples with a specific driver mutation.

“To exclude the possibility that the high frequency of cells with detected NPM1 mutations affected the classifier, we generated a separate classifier that does not consider NPM1 mutant calls. This separate classifier had equally high specificity (99.8% of normal cells correctly called normal), and sensitivity (93% of malignant cells correctly called malignant) in 5-fold cross-validation. It is also consistent with the original classifier: 97% of cells originally classified as normal were classified as normal; 91% of cells originally classified as malignant were classified as malignant. These results indicate that the classifier is robust to the frequency of NPM1 mutations in the training set.”

Should have put the NPM1 sample in the non-NPM1 trained classifier.

External test dataset

scRNA-seq from AML patients (NPM1) with sc genotyping.

Naldini, M.M., Casirati, G., Barcella, M. et al. Longitudinal single-cell profiling of chemotherapy response in acute myeloid leukemia. Nat Commun 14, 1285 (2023). https://doi.org/10.1038/s41467-023-36969-0

10 patients with NPM1mut AML, present in van Galen dataset. Majority of NPM1mut AML sampels in van Galen dataset also have DNMT3 mutation. DNMT3 status is not mentioned for this study.

3 patients with del(7) AML, not present in van Galen data.

Preprocessing information Feature-barcodes filtered matrices from Cell Ranger were used as input for Seurat R package64,65 (version 3.2.3). Seurat objects were merged in a single full dataset. Cells with a mitochondrial count ratio higher than 0.2 and <100 or >7000 expressed genes were removed from the dataset. UMI counts were log normalized and scaled for a factor of 10,000.
The top 20% most variable genes were selected for downstream analysis. Cell cycle scores were assigned with the CellCycleScoring function using a reference gene lists66. We scaled data and regressed out unwanted variability by passing UMI count, percent of mitochondrial genes and cell cycle difference defined variables to the vars.to.regress argument. Cell cycle difference was defined as the difference between S phase and G/2 M phase module scores. Downstream analysis was performed on the top 100 principal components (PCA). In order to reduce patient-related and 10x chemistry version (v2 vs v3) bias, we performed data integration using the Harmony package (v1.0)24.

Mutation annotation information NPM1-MF considers the UMI counts supporting either NPM1 mutant or WT allele to classify cells as MUT (≥1 UMI for the mutant allele) WT (>5 WT transcripts, no mutant transcripts) ND – not detected (cells with ≤ 5 WT transcripts and no mutant transcripts) NoCall (cells without coverage over the NPM1 mutation region)

Check patient metadata.
Do not provide information on age, Sex or clinincal blast count.

metadata_npm1_patients <- readxl::read_xlsx("naldini_et_al/41467_2023_36969_MOESM5_ESM.xlsx")
metadata_npm1_patients

Check cell metadata.
I can’t figure out what the orig.ident (e.g. M03) is. It is the ID used in the expression matrix. So maybe the capture, but it doesn’t make much sense to have 9 cells from PT12 and 570 cells from PT13 in M44. Why would there be leakage between captures.

PT01, PT02, PT13 are primary refractory. PT06, PT07, PT12 are long-term complete remission. PT08, PT09, PT10, PT15 are NPM1mut AML relapse post-chemotherapy.

metadata_npm1_cells <- readRDS("naldini_et_al/GSE185991_Full_Patient_metadata.rds")
head(metadata_npm1_cells)

Check number of genotyped cells per sample to pick one for testing.

PT02, combining diagnosis and D30 samples, has a lot of WT and MUT cells, NPM1mut, primary refractory. Good patient to test if classifier can extrapolate to other scRNA-seq datasets.

metadata_npm1_patients %>%
  mutate(Sample = paste(PatientID, Timepoint, sep = "_")) %>%
  group_by(Sample, Classification) %>%
  summarize(n_cells = sum(n)) %>%
  ggplot(aes(x = Sample, y = n_cells, fill = Classification)) +
  geom_bar(stat = "identity") +
  theme_bw() +
  theme(panel.grid = element_blank(), axis.text.x = element_text(angle = 45, hjust = 1))
`summarise()` has grouped output by 'Sample'. You can override using the `.groups` argument.

Captures with PT02 data. Not sure why there are only 9 cells in the other capture.

metadata_npm1_cells %>%
  filter(PatientID == "PT02") %>%
  pull(orig.ident) %>%
  table()
.
 M25  M26 
1563 2152 
metadata_npm1_cells %>%
  filter(PatientID == "PT07") %>%
  pull(orig.ident) %>%
  table()
.
 M21  M22  M23  M24 
 520 3706  603 6389 

Read in count data.

naldini_seurat_m25 <- Read10X(
  data.dir = "naldini_et_al/GSE185991_RAW/M25/",
  gene.column = 2,
  cell.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
colnames(naldini_seurat_m25) <- paste0("M25_", gsub("-1", "", colnames(naldini_seurat_m25)))

naldini_seurat_m26 <- Read10X(
  data.dir = "naldini_et_al/GSE185991_RAW/M26/",
  gene.column = 2,
  cell.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
colnames(naldini_seurat_m26) <- paste0("M26_", gsub("-1", "", colnames(naldini_seurat_m26)))

# combine captures and merge
naldini_seurat_pt02 <- cbind(naldini_seurat_m25, naldini_seurat_m26)
naldini_seurat_pt02 <- CreateSeuratObject(counts = naldini_seurat_pt02, min.cells = 0, min.features = 200)
ncol(naldini_seurat_pt02)
[1] 4677
naldini_seurat_m25 <- NULL
naldini_seurat_m26 <- NULL
gc()
             used    (Mb) gc trigger    (Mb) limit (Mb)   max used    (Mb)
Ncells   14875814   794.5   22323122  1192.2         NA   22323122  1192.2
Vcells 5053453562 38554.8 9354963862 71372.8     102400 9327473519 71163.0
# subset to cells present in metadata
naldini_seurat_pt02 <- subset(naldini_seurat_pt02, cells = rownames(metadata_npm1_cells))
ncol(naldini_seurat_pt02)
[1] 3714
# add metadata to Seurat object
naldini_seurat_pt02 <- AddMetaData(naldini_seurat_pt02, metadata_npm1_cells)
table(naldini_seurat_pt02$orig.ident)

 M25  M26 
1563 2151 
naldini_seurat_m21 <- Read10X(
  data.dir = "naldini_et_al/GSE185991_RAW/M21/",
  gene.column = 2,
  cell.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
colnames(naldini_seurat_m21) <- paste0("M21_", gsub("-1", "", colnames(naldini_seurat_m21)))

naldini_seurat_m22 <- Read10X(
  data.dir = "naldini_et_al/GSE185991_RAW/M22/",
  gene.column = 2,
  cell.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
colnames(naldini_seurat_m22) <- paste0("M22_", gsub("-1", "", colnames(naldini_seurat_m22)))

naldini_seurat_m23 <- Read10X(
  data.dir = "naldini_et_al/GSE185991_RAW/M23/",
  gene.column = 2,
  cell.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
colnames(naldini_seurat_m23) <- paste0("M23_", gsub("-1", "", colnames(naldini_seurat_m23)))

naldini_seurat_m24 <- Read10X(
  data.dir = "naldini_et_al/GSE185991_RAW/M24/",
  gene.column = 2,
  cell.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
colnames(naldini_seurat_m24) <- paste0("M24_", gsub("-1", "", colnames(naldini_seurat_m24)))

# combine captures and merge
naldini_seurat_pt07 <- cbind(naldini_seurat_m21, naldini_seurat_m22, naldini_seurat_m23, naldini_seurat_m24)
naldini_seurat_pt07 <- CreateSeuratObject(counts = naldini_seurat_pt07, min.cells = 0, min.features = 200)
ncol(naldini_seurat_pt07)
[1] 13396
naldini_seurat_m21 <- NULL
naldini_seurat_m22 <- NULL
naldini_seurat_m23 <- NULL
naldini_seurat_m24 <- NULL
gc()
             used    (Mb) gc trigger    (Mb) limit (Mb)   max used    (Mb)
Ncells   14878898   794.7   22323122  1192.2         NA   22323122  1192.2
Vcells 5073623895 38708.7 9354963862 71372.8     102400 9327473519 71163.0
# subset to cells present in metadata
naldini_seurat_pt07 <- subset(naldini_seurat_pt07, cells = rownames(metadata_npm1_cells))
ncol(naldini_seurat_pt07)
[1] 11216
# add metadata to Seurat object
naldini_seurat_pt07 <- AddMetaData(naldini_seurat_pt07, metadata_npm1_cells)
table(naldini_seurat_pt07$orig.ident)

 M21  M22  M23  M24 
 520 3704  603 6389 

Filter cells for >3000 UMI, 1000 genes, <10% mitochondrial transcripts PT02. PT07 seems to have lower quality. Use less stringent filters.

VlnPlot(naldini_seurat_pt02, features = c("nCount_RNA", "nFeature_RNA", "percent.mt"), log = T, pt.size = 0)
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.

VlnPlot(naldini_seurat_pt07, features = c("nCount_RNA", "nFeature_RNA", "percent.mt"), log = T, pt.size = 0)
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.

Seems like PT02 is female, PT07 is male.

VlnPlot(naldini_seurat_pt02, features = c("XIST"))
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.

VlnPlot(naldini_seurat_pt07, features = c("XIST"))
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.Warning: All cells have the same value of XIST.

naldini_seurat_pt02 <-
  subset(naldini_seurat_pt02,
         subset = nCount_RNA > 3000 & nFeature_RNA > 1000 & percent.mt < 10)
naldini_seurat_pt07 <-
  subset(naldini_seurat_pt07,
         subset = nCount_RNA > 1000 & nFeature_RNA > 500 & percent.mt < 10)
table(naldini_seurat_pt02$orig.ident)

 M25  M26 
1347 1916 
table(naldini_seurat_pt07$orig.ident)

 M21  M22  M23  M24 
 359 2498  563 5736 
naldini_seurat_pt02_pt07 <-
  merge(naldini_seurat_pt02, naldini_seurat_pt07)
naldini_seurat_pt02_pt07
An object of class Seurat 
33538 features across 12419 samples within 1 assay 
Active assay: RNA (33538 features, 0 variable features)
 2 layers present: counts.1, counts.2
naldini_seurat_pt02_pt07 <-
  merge(
    naldini_seurat_pt02,
    y = naldini_seurat_pt07,
    add.cell.ids = c("PT02", "PT07"),
    project = "Naldini"
  )
naldini_seurat_pt02_pt07
An object of class Seurat 
33538 features across 12419 samples within 1 assay 
Active assay: RNA (33538 features, 0 variable features)
 2 layers present: counts.1, counts.2

Normalize gene expression.

naldini_seurat_pt02_pt07 <- NormalizeData(naldini_seurat_pt02_pt07)
Normalizing layer: counts.1
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Normalizing layer: counts.2
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Predict HSPC cell type
X <-
  cbind(
    naldini_seurat_pt02_pt07@assays$RNA@layers$data.1,
    naldini_seurat_pt02_pt07@assays$RNA@layers$data.2
  )
rownames(X) <- rownames(naldini_seurat_pt02_pt07)

Some features used in classifier are missing in data, either due to gene ID mismatches or because the genes are not expressed. Both van Galen and Naldini were aligned to hg38, so it is more likely lack of expression. Will set missing genes to 0.

features_missing <-
  rownames(rf.celltype.inner$importance)[!rownames(rf.celltype.inner$importance) %in% rownames(X)]

features_missing_mat <-
  matrix(nrow = length(features_missing),
         ncol = ncol(X),
         data = 0)
rownames(features_missing_mat) <- features_missing
colnames(features_missing_mat) <- colnames(X)

X <- rbind(X, features_missing_mat)

X <- X[rownames(rf.celltype.inner$importance), ]
naldini_seurat_pt02_pt07_celltype_pred <- predict(rf.celltype.inner, newdata = t(X), type = "prob")
Predict malignant cells
X <-
  cbind(
    naldini_seurat_pt02_pt07@assays$RNA@layers$data.1,
    naldini_seurat_pt02_pt07@assays$RNA@layers$data.2
  )
X <- rbind(X, t(naldini_seurat_pt02_pt07_celltype_pred))
rownames(X) <-
  c(
    rownames(naldini_seurat_pt02_pt07),
    colnames(naldini_seurat_pt02_pt07_celltype_pred)
  )

features_missing <-
  rownames(rf.malignant.celltype$importance)[!rownames(rf.malignant.celltype$importance) %in% rownames(X)]
features_missing_mat <-
  matrix(nrow = length(features_missing),
         ncol = ncol(X),
         data = 0)
rownames(features_missing_mat) <- features_missing
colnames(features_missing_mat) <- colnames(X)

X <- rbind(X, features_missing_mat)
naldini_seurat_pt02_pt07_malignant_pred <-
  predict(rf.malignant.celltype,
          newdata = t(X),
          type = "prob")
Warning: sparse->dense coercion: allocating vector of size 1.1 GiB
naldini_seurat_pt02_pt07_malignant_pred_max <-
  colnames(naldini_seurat_pt02_pt07_malignant_pred)[max.col(naldini_seurat_pt02_pt07_malignant_pred)]
Results

Compare malignant vs. healthy prediction with genotype data.

naldini_seurat_pt02_malignant_pred_max <- naldini_seurat_pt02_pt07_malignant_pred_max[naldini_seurat_pt02_pt07$PatientID == "PT02"]
table(
  naldini_seurat_pt02_malignant_pred_max,
  naldini_seurat_pt02@meta.data$Classification
)
                                      
naldini_seurat_pt02_malignant_pred_max  MUT   ND NoCall   WT
                             healthy      9  410     38  578
                             malignant 1118  717    190  203
round(t(t(
  table(
    naldini_seurat_pt02_malignant_pred_max,
    naldini_seurat_pt02@meta.data$Classification
  )
) / as.vector(
  table(naldini_seurat_pt02@meta.data$Classification)
)), 3)
                                      
naldini_seurat_pt02_malignant_pred_max   MUT    ND NoCall    WT
                             healthy   0.008 0.364  0.167 0.740
                             malignant 0.992 0.636  0.833 0.260
naldini_seurat_pt07_malignant_pred_max <-
  naldini_seurat_pt02_pt07_malignant_pred_max[naldini_seurat_pt02_pt07$PatientID == "PT07"]
table(
  naldini_seurat_pt07_malignant_pred_max,
  naldini_seurat_pt07@meta.data$Classification
)
                                      
naldini_seurat_pt07_malignant_pred_max  MUT   ND NoCall   WT
                             healthy     10 2166   2894  121
                             malignant  775  922   1816  452
round(t(t(
  table(
    naldini_seurat_pt07_malignant_pred_max,
    naldini_seurat_pt07@meta.data$Classification
  )
) / as.vector(
  table(naldini_seurat_pt07@meta.data$Classification)
)), 3)
                                      
naldini_seurat_pt07_malignant_pred_max   MUT    ND NoCall    WT
                             healthy   0.013 0.701  0.614 0.211
                             malignant 0.987 0.299  0.386 0.789

Test on sample with a mutation not present in van Galen data.

del(7) AML: deletion in chromosome 7.

Classification of del(7) cells into malignant/wt:
“We leveraged the AddModuleScore function for evaluating the expression level of a Chr 7 signature by using as input gene list all genes located on it. The observed distribution of Chr 7 module scores in the datasets followed a bimodal distribution allowing us to classify cells as AML or non-AML by running a k-means clustering (n = 2) on the vector of Chr 7 signature scores and labelling cells in the high score group as non-AML and those in the low score group as AML.”

Unfortunately, not included in metadata.

PT11, PT17: refractory disease; PT18: early relapse

metadata_del7_cells <- readRDS("naldini_et_al/GSE185991_AML_Del7_metadata.rds")

table(metadata_del7_cells$orig.ident, metadata_del7_cells$PatientID)
     
      PT11 PT17 PT18
  M36 6665    0    0
  M38 1942    0    0
  M84    0 7924    0
  M85    0 1978    0
  M88    0    0 5847
  M89    0    0 3440
  M92    0 5512    0

Computing module score with all genes on chr7 might be very slow.

Download cellranger gtf reference, get all genes on chr7.

source="/Users/holzehenrietta/Documents/personal/phd_applications/muds_task_marr/reference_sources"
mkdir -p "$source"
gtf_url="http://ftp.ebi.ac.uk/pub/databases/gencode/Gencode_human/release_44/gencode.v44.primary_assembly.annotation.gtf.gz"
gtf_in="${source}/gencode.v44.primary_assembly.annotation.gtf"

if [ ! -f "$gtf_in" ]; then
    curl -sS "$gtf_url" | zcat > "$gtf_in"
fi

grep "^chr7" $gtf_in | awk '$3 == "gene"' | sed 's/.*gene_name "\([^"]*\)".*/\1/g' > ${source}/gencode.v44_gene_names_chr7.txt

grep "^chrY" $gtf_in | awk '$3 == "gene"' | sed 's/.*gene_name "\([^"]*\)".*/\1/g' > ${source}/gencode.v44_gene_names_chrY.txt
chr7_genes <-
  read_lines(
    "/Users/holzehenrietta/Documents/personal/phd_applications/muds_task_marr/reference_sources/gencode.v44_gene_names_chr7.txt"
  )

Subset to genes expressed in >= 10% of cells in del(7) scRNA-seq data to speed up analysis.

Save Session Info

writeLines(capture.output(sessionInfo()), paste0(Sys.Date(), "_sessionInfo.txt"))
LS0tCnRpdGxlOiAiTVVEUyBNYXJyIEV4ZXJjaXNlIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAphdXRob3I6IEhlbnJpZXR0YSBIb2x6ZQpkYXRlOiAiMjEgTm92ZW1iZXIgMjAyNCIKLS0tCgojIyBUYXNrIGRlc2NyaXB0aW9uCgpJbiBhIDIwMTkgcHVibGljYXRpb24gKGRvaToxMC4xMDE2L2ouY2VsbC4yMDE5LjAxLjAzMSksIFBldGVyIHZhbiBHYWxlbiBhbmQgY29sbGVhZ3VlcyBhbmFseXplZCB0cmFuc2NyaXB0b21pYyBwcm9maWxlcyBvZiBib25lIG1hcnJvdyBhc3BpcmF0ZXMgZnJvbSAxNiBBTUwgcGF0aWVudHMgYW5kIGZpdmUgaGVhbHRoeSBkb25vcnMuCgpZb3VyIGZpcnN0IHRhc2sgaW4gdGhpcyBleGVyY2lzZSBpcyB0byBwcm92aWRlIGEgY29uY2lzZSBvdmVydmlldyBvZiB0aGUgZGF0YXNldCwgaW5jbHVkaW5nIHN1bW1hcnkgc3RhdGlzdGljcyBzdWNoIGFzIHRoZSBjb21wb3NpdGlvbiBvZiBjZWxsLXR5cGUgZnJlcXVlbmNpZXMgYWNyb3NzIGRvbm9ycyBhbmQgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBkYXRhIGluIHR3byBkaW1lbnNpb25zIChlLmcuLCB1c2luZyBVTUFQIG9yIHQtU05FIGVtYmVkZGluZ3MpLiAKCkFuIGludGVyZXN0aW5nIGZlYXR1cmUgb2YgdGhlIGRhdGFzZXQgaXMgdGhlIGF2YWlsYWJpbGl0eSBvZiBsYWJlbHMgZGlzdGluZ3Vpc2hpbmcgbWFsaWduYW50IGFuZCBub24tbWFsaWduYW50IHNpbmdsZSBjZWxscy4gVGhlIHNlY29uZCB0YXNrIGlzIHRvIGV4cGxvaXQgdGhlc2UgbGFiZWxzIHRvIGJ1aWxkIGEgc3VpdGFibGUgTUwgY2xhc3NpZmllciB0aGF0IHByZWRpY3RzIHRoZSBub3JtYWwgb3IgbWFsaWduYW50IGlkZW50aXR5IG9mIHNpbmdsZSBjZWxscy4gClBsZWFzZSBkZXRhaWwgb24gaG93IHlvdXIgY2xhc3NpZmllciB3b3Jrcywgd2hldGhlciBpdCBpcyBpbnRlcnByZXRhYmxlLCBpdHMgcGVyZm9ybWFuY2UsIGFuZCB0aGUgdHJhaW4tdGVzdCBzcGxpdCB5b3UgdXNlIGZvciB5b3VyIG1hY2hpbmUgbGVhcm5pbmcgZXhwZXJpbWVudC4gIAoKCiMjIyBSZWxldmFuY2Ugb2YgdGhlIHByb2JsZW0KCioqVGFzayAxKioKCkl0IGlzIGltcG9ydGFudCB0byBnZXQgYSBnb29kIHVuZGVyc3RhbmRpbmcgb2YgYSBkYXRhc2V0IGJlZm9yZSB0cmFpbmluZyBhbnkga2luZCBvZiBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBvbiBpdC4gCkltcG9ydGFudCBmYWN0b3JzIHRvIGJlIGF3YXJlIG9mIGFyZSBiaWFzZXMgaW4gdGhlIGRhdGEsIGluIHBhcnRpY3VsYXIsIGJpYXNlcyBpbiB0aGUgcGF0aWVudCBtZXRhZGF0YS4gIApGdXJ0aGVybW9yZSwgdGhlIHF1YWxpdHkgb2YgdGhlIGRhdGEgaGFzIHRvIGJlIGFzc2Vzc2VkLCBzdWNoIGFzIHNhbXBsZSBhbmQgY2VsbCBxdWFsaXR5LiAgCkxhc3RseSwgdHJhbnNjcmlwdG9taWNzIGRhdGEgaXMgaGlnaGx5IGRpbWVuc2lvbmFsIGFuZCBpbiBvcmRlciB0byByZWNvZ25pemUgcGF0dGVybnMgaW4gdGhlIGRhdGEsIGl0IGlzIHVzZWZ1bCB0byBlbWJlZCB0aGUgZGF0YSBpbnRvIGEgMkQgc3BhY2UgdGhhdCBjYW4gYmUgdmlzdWFsaXplZC4gCkVtYmVkZGluZyBvZiBkYXRhIGludG8gYSBsb3dlciBkaW1lbnNpb25hbCBzcGFjZSwgZS5nLiB2aWEgUENBLCBhbHNvIHNlcnZlcyB0byByZW1vdmUgbm9pc2UuCgoqKlRhc2sgMioqCgpEZXBlbmRpbmcgb24gZGlzZWFzZSBidXJkZW4sIHRoZXJlIGFyZSBoZWFsdGh5IGFuZCBtYWxpZ25hbnQgY2VsbHMgcHJlc2VudCBpbiB0aGUgYm9uZSBtYXJyb3cgb2YgcGF0aWVudHMgd2l0aCBBTUwuICAKSW4gb3JkZXIgdG8gaW52ZXN0aWdhdGUgZGlzZWFzZSBtZWNoYW5pc21zIG9mIEFNTCwgd2UgbmVlZCB0byBiZSBhYmxlIHRvIGNsZWFybHkgZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIGhlYWx0aHkgYW5kIG1hbGlnbmFudCBjZWxscy4gCk9ubHkgdGhlbiwgd2UgY2FuIGxvb2sgYXQgd2hhdCBiaW9sb2dpY2FsIHByb2Nlc3NlcyBpbiBjZWxsIGRpZmZlcmVudGlhdGlvbiBhcmUgZGlzcnVwdGVkLiAKVGhlIGRpZmZlcmVudGlhdGlvbiBpcyBhbHNvIGltcG9ydGFudCB0byBpZGVudGlmeSB0cmVhdG1lbnQgdGFyZ2V0cyB0aGF0IGRvIG5vdCBraWxsIHRoZSByZW1haW5pbmcgaGVhbHRoeSBIU1BDcy4gIApQYXJ0aWN1bGFybHkgaW4gTVJEIHNhbXBsZXMgaXQgaXMgdmVyeSBpbXBvcnRhbnQgdG8gaWRlbnRpZnkgdGhlIHN1YnNldCBvZiBtYWxpZ25hbnQgY2VsbHMgdGhhdCBpcyByZXNpc3RhbnQvdG9sZXJhbnQgdG8gdHJlYXRtZW50LiAgCkluIEFNTCwgaGVhbHRoeSBhbmQgbWFsaWduYW50IHByb2dlbml0b3IgYW5kIG15ZWxvaWQgY2VsbHMgYXJlIG5vdCBkaXN0aW5ndWlzaGFibGUgYnkgY2VsbCBzdXJmYWNlIG1hcmtlcnMgaS5lLiwgaXQgaXMgbm90IHBvc3NpYmxlIHRvIGlzb2xhdGUgaGVhbHRoeS9tYWxpZ25hbnQgY2VsbHMgYnkgRkFDUyAoeWV0KS4gIApUaHVzLCBzY1JOQS1zZXEgZGF0YSBvZiBBTUwgQk0gc2FtcGxlcyBjb250YWluIGJvdGggZ3JvdXBzIG9mIGNlbGxzIGFuZCB3ZSBoYXZlIHRodXMgZmFyIG5vdCBiZWVuIGFibGUgdG8gZGlzdGluZ3Vpc2ggdGhlbSBiYXNlZCBvbiB0aGVpciB0cmFuc2NyaXB0b21lLgoKVmFuIEdhbGVuIGV0IGFsLiBlbXBsb3llZCBhIG5vdmVsIHRlY2hub2xvZ3kgdG8gZ2Vub3R5cGUgYSBzdWJzZXQgb2YgY2VsbHMgcHJlc2VudCBpbiB0aGUgc2NSTkEtc2VxIGRhdGEgYW5kIHVzZWQgdGhlc2UgZ2Vub3R5cGVkIGNlbGxzIHRvIGJ1aWxkIGEgY2xhc3NpZmllciB0byBkaXN0aW5ndWlzaCB0aGUgYnVsayBvZiB0aGUgY2VsbHMgaW50byBtYWxpZ25hbnQgYW5kIG5vbi1tYWxpZ25hbnQgY2VsbHMuICAKSG93ZXZlciwgc2MgZ2Vub3R5cGluZyBjb21lcyBhdCBhbiBleHRyYSBjb3N0IGFuZCBsYWJvciBhbmQgaXMgbm90IGF2YWlsYWJsZSBmb3IgYWxsIGRhdGFzZXRzLiAKVGhlcmVmb3JlLCBpdCB3b3VsZCBiZSBleHRyZW1lbHkgdXNlZnVsIHRvIGhhdmUgYSBnZW5lcmFsIGNsYXNzaWZpZXIgb2Ygbm9ybWFsIHZzLiBtYWxpZ25hbnQgY2VsbHMgaW4gQU1MIHNhbXBsZXMsIHRoYXQgaXMgYXBwbGljYWJsZSB0byBvdGhlciBzY1JOQS1zZXEgZGF0YXNldHMuIAoKIyMjIHZhbiBHYWxlbiAyMDE5IGRhdGFzZXQKCltQYXBlcl0oaHR0cHM6Ly93d3cuY2VsbC5jb20vY2VsbC9mdWxsdGV4dC9TMDA5Mi04Njc0KDE5KTMwMDk0LTcpCgpbT3JpZ2luYWwgYW5hbHlzaXMgb2YgdGhlIGRhdGFdKGh0dHBzOi8vZ2l0aHViLmNvbS9CZXJuc3RlaW5MYWIvYW1sMjAxOSkKCltSZS1hbmFseXNpcyBvZiBkYXRhXShodHRwczovL2dpdGh1Yi5jb20vcGV0ZXJ2YW5nYWxlbi9yZWFuYWx5emUtYW1sMjAxOSkKCkRhdGEgZG93bmxvYWRlZCB3aXRoIGB3Z2V0IGh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvMzk5eDA0NXpjNTdmaXV0L1NldXJhdF9BTUwucmRzYAoKTVVUWi0zIGFuZCBPQ0ktQU1MMyBhcmUgQU1MIGNlbGwgbGluZXMuCgpCTSBzYW1wbGVzIGFyZSBmcm9tIGhlYWx0aHkgZG9ub3JzLiAKQ2VsbHMgZnJvbSBvbmUgaGVhbHRoeSBkb25vciB3ZXJlIHNvcnRlZCBmb3IgQ0QzNCsgKEhTUENzKSBhbmQgQ0QzNCtDRDM4LSAoSFNDcyksIHRvIGVucmljaCBmb3IgcmFyZSBlYXJseSBwcm9nZW5pdG9yIHBvcHVsYXRpb25zLgoKQWxsIG90aGVyIHNhbXBsZXMgYXJlIGZyb20gcGF0aWVudHMgZGlhZ25vc2VkIHdpdGggQU1MLCB3aXRoIGRpZmZlcmVudCBkcml2ZXIgbXV0YXRpb25zLiAKCkFNTCBzYW1wbGVzIHdlcmUgY29sbGVjdGVkIGF0IGRheSAwIGFuZCBmb3IgYSBzdWJzZXQgb2Ygc2FtcGxlcyBhdCBkaWZmZXJlbnQgdGltZSBwb2ludHMgcG9zdCBkaWFnbm9zaXMgKGFuZCBwb3N0IGNoZW1vKS4KCiMjIFRhc2sgMTogRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcwoKYGBge3IgcmVzdWx0cz1GfQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGNoYW1lbGVvbikKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmBgYAoKCjwhLS0gYGBge3J9IC0tPgo8IS0tICMgbGlicmFyeSgicmV0aWN1bGF0ZSIpIC0tPgo8IS0tICMgcHlfaW5zdGFsbCgicHl0aG9uLWlncmFwaCIpIC0tPgo8IS0tICMgcHlfaW5zdGFsbCgibGVpZGVuYWxnIiwgZm9yZ2UgPSBUUlVFKSAtLT4KPCEtLSAjIGluc3RhbGwucGFja2FnZXMoImxlaWRlbiIpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIyBQcmVwcm9jZXNzIG1ldGFkYXRhCgpMb2FkIHNjUk5BLXNlcSBkYXRhIG9iamVjdCBhbmQgbWV0YWRhdGEuIAoKYGBge3J9CmdhbGVuX2FtbCA8LSByZWFkUkRTKCJyZWFuYWx5emUtYW1sMjAxOS9TZXVyYXRfQU1MLnJkcyIpCmRvbm9yX21ldGFkYXRhIDwtCiAgcmVhZHhsOjpyZWFkX3hsc3goCiAgICAiU2NpZW5jZURpcmVjdF9maWxlc18yMU5vdjIwMjRfMDgtMDktMzAuOTg3LzEtczIuMC1TMDA5Mjg2NzQxOTMwMDk0Ny1tbWMxLnhsc3giCiAgKQpgYGAKCkxvYWQgSFNQQyBjZWxsIHR5cGVzIGFubm90YXRlZCBieSBiYWNrc3BpbiBjbHVzdGVyIGFuZCBhZGQgdG8gbWV0YWRhdGEgb2YgU2V1cmF0IG9iamVjdC4KRnJvbSBbYW1sMjAxOSBnaXRodWIgcmVwb10oaHR0cHM6Ly9naXRodWIuY29tL0Jlcm5zdGVpbkxhYi9hbWwyMDE5L3RyZWUvbWFzdGVyKS4KCmBgYHtyIHJlc3VsdHM9Rn0KYmFja3NwaW5fY2VsbHR5cGVzIDwtCiAgcmVhZF90c3YoImFtbDIwMTkvMDQgUmFuZG9tIGZvcmVzdCBjbGFzc2lmaWVyL0JNXzY5MTVjZWxscy5CYWNrU1BJTi50eHQiKQoKYmFja3NwaW5fY2VsbHR5cGVzIDwtCiAgY29sdW1uX3RvX3Jvd25hbWVzKGJhY2tzcGluX2NlbGx0eXBlcywgdmFyID0gImNlbGwiKSAlPiUgZHBseXI6OnJlbmFtZShiYWNrc3Bpbl9jZWxsdHlwZSA9IGNsdXN0ZXIpCnJvd25hbWVzKGJhY2tzcGluX2NlbGx0eXBlcykgPC0gZ3N1YigiLSIsICJcXC4iLCByb3duYW1lcyhiYWNrc3Bpbl9jZWxsdHlwZXMpKQoKZ2FsZW5fYW1sIDwtIEFkZE1ldGFEYXRhKGdhbGVuX2FtbCwgbWV0YWRhdGEgPSBiYWNrc3Bpbl9jZWxsdHlwZXMpCmBgYAoKCmBgYHtyfQpoZWFkKGdhbGVuX2FtbEBtZXRhLmRhdGEpCmhlYWQoZG9ub3JfbWV0YWRhdGEpCmBgYAoKTWV0YWRhdGEgaW5jbHVkZWQgaW4gU2V1cmF0IG9iamVjdAotIG9yaWcuaWRlbnQgKHBhdGllbnQgSUQgKyBkYXkgZnJvbSBkaWFnbm9zaXMpCi0gTnVtYmVyT2ZSZWFkcwotIEN5Y2xpbmdTY29yZQotIEN5Y2xpbmdCaW5hcnkKLSBNdXRUcmFuc2NyaXB0cyAobnVtYmVyIG9mIHRyYW5zY3JpcHQgZm9yIHd0IGFsbGVsZSkKLSBXdFRyYW5zY3JpcHRzIChudW1iZXIgb2YgdHJhbnNjcmlwdCBmb3IgbXV0YXRlZCBhbGxlbGUpCi0gUHJlZGljdGlvblJlZmluZWQgKGNsYXNzaWZpY2F0aW9uIG9mIGNlbGxzIGludG8gbWFsaWduYW50LCBoZWFsdGh5IGFuZCB1bmNlcnRhaW4pCi0gQ2VsbFR5cGUgKGNsYXNzaWZpY2F0aW9uIGludG8gMjEgaGVhbHRoeSBhbmQgbWFsaWduYW50IGNlbGwgdHlwZXMpCi0gbkNvdW50X1JOQQotIG5GZWF0dXJlX1JOQQoKClJlbW92ZSBjZWxscyBhbmQgbWV0YWRhdGEgYXNzb2NpYXRlZCB0byBjZWxsIGxpbmVzLgoKYGBge3J9CmdhbGVuX2FtbCRTYW1wbGVDbGFzcyA8LQogIGlmZWxzZSgKICAgIGdyZXBsKCJCTSIsIGdhbGVuX2FtbCRvcmlnLmlkZW50KSwKICAgICJIZWFsdGh5RG9ub3IiLAogICAgaWZlbHNlKAogICAgICBncmVwbCgiT0NJLkFNTDN8TVVUWjMiLCBnYWxlbl9hbWwkb3JpZy5pZGVudCksCiAgICAgICJDZWxsTGluZSIsCiAgICAgICJBbWxEb25vciIKICAgICkKICApCmdhbGVuX2FtbF9kb25vcnMgPC0gc3Vic2V0KGdhbGVuX2FtbCwgU2FtcGxlQ2xhc3MgIT0gIkNlbGxMaW5lIikKCmRvbm9yX21ldGFkYXRhIDwtIGRvbm9yX21ldGFkYXRhICU+JQogIGZpbHRlcighU2FtcGxlICVpbiUgYygiT0NJLUFNTDMiLCAiTVVUWjMiKSkKYGBgCgpDb3JyZWN0IHZhbHVlIGluIGJsYXN0IGNvdW50IG1ldGFkYXRhLiAKIjElICg3NiUgcHJvbW9uby1jeXRlcykiIGlzIGNvbnNpZGVyZWQgYXMgYmxhc3QgY291bnQgb2YgNzYlIGluIHRoZSBwYXBlciAoRmlnLiAyYikuCgpgYGB7cn0KZG9ub3JfbWV0YWRhdGEkYEJsYXN0IGNvdW50YCA8LQogIGFzLm51bWVyaWMoZ3N1YigiPDVcXCUiLCAiMC4wNSIsIGdzdWIoCiAgICAiPDFcXCUiLAogICAgIjAuMDEiLAogICAgZ3N1YigiLio3NlxcJS4qIiwgIjAuNzYiLCBkb25vcl9tZXRhZGF0YSRgQmxhc3QgY291bnRgKQogICkpKQpkb25vcl9tZXRhZGF0YSRBZ2UgPC0gYXMubnVtZXJpYyhkb25vcl9tZXRhZGF0YSRBZ2UpCmBgYAoKCk1lcmdlIGRvbm9yIG1ldGFkYXRhIGludG8gU2V1cmF0IG9iamVjdCBtZXRhZGF0YS4KCmBgYHtyfQojIG1hdGNoIHNhbXBsZSBJRCBiZXR3ZWVuIFNldXJhdCBvYmplY3QgYW5kIG1ldGFkYXRhIHRhYmxlCmRvbm9yX21ldGFkYXRhIDwtIGRvbm9yX21ldGFkYXRhICU+JQogIG11dGF0ZShTYW1wbGVJZCA9IGdzdWIoIkNEIiwgIiIsIGdzdWIoIiAiLCAiXFwuIiwgZ3N1YigKICAgICJcXCsiLCAicCIsIGdzdWIoIi0iLCAibiIsIFNhbXBsZSkKICApKSkpICU+JQogIG11dGF0ZShTYW1wbGVJZCA9IGlmZWxzZSgKICAgIGdyZXBsKCJCTSIsIFNhbXBsZUlkKSwKICAgIFNhbXBsZUlkLAogICAgcGFzdGUoU2FtcGxlSWQsIGBEYXlzIGZyb20gZGlhZ25vc2lzYCwgc2VwID0gIi4iKQogICkpCgojIG1lcmdlIGludG8gc2V1cmF0IG9iamVjdCBtZXRhZGF0YSwgcHJlc2VydmUgb3JkZXIgb2YgY2VsbHMKdG1wIDwtCiAgbWVyZ2UoCiAgICByb3duYW1lc190b19jb2x1bW4oZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEpLAogICAgZG9ub3JfbWV0YWRhdGEsCiAgICBieS54ID0gIm9yaWcuaWRlbnQiLAogICAgYnkueSA9ICJTYW1wbGVJZCIsCiAgICBhbGwueCA9IFQKICApCnRtcCA8LSBjb2x1bW5fdG9fcm93bmFtZXModG1wLCAicm93bmFtZSIpCmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhIDwtIHRtcFtjb2xuYW1lcyhnYWxlbl9hbWxfZG9ub3JzKSxdCmBgYAoKCiMjIyBTZXggZGlzdHJpYnV0aW9uCgpUZXR0ZXJvLCBKLk0uLCBDbG9vcywgSi4gJiBCdWxsaW5nZXIsIEwuIEFjdXRlIG15ZWxvaWQgbGV1a2VtaWE6IGRvZXMgc2V4IG1hdHRlcj8uIExldWtlbWlhIDM4LCAyMzI54oCTMjMzMSAoMjAyNCkuIFtodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDEzNzUtMDI0LTAyNDM1LXpdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxMzc1LTAyNC0wMjQzNS16L3RhYmxlcy8xKQogICAgCkFNTCBpcyBtb3JlIGZyZXF1ZW50IGluIG1hbGVzIChjYS4gNC41IHBlciAxMDAsMDAwKSB2cyBmZW1hbGVzIChjYS4gMy4wIHBlciAxMDAsMDAwKS4KQSBudW1iZXIgb2Ygc3R1ZGllcyBzaG93aW5nIGRpZmZlcmVuY2VzIGluIHRyZWF0bWVudCByZXNwb25zZSBpbiBtYWxlcyBhbmQgZmVtYWxlcyBkZW1vbnN0cmF0ZXMgdGhlIGltcG9ydGFuY2UgdG8gY29uc2lkZXIgc2V4LgoKVGhlIHZhbiBHYWxlbiBkYXRhc2V0IG9ubHkgaGFzIG1hbGUgaGVhbHRoeSBkb25vcnMgYW5kIGlzIGJpYXNlZCB0b3dhcmRzIG1hbGUgQU1MIGRvbm9ycyAoNjIuNSUpLiAKU2xpZ2h0bHkgbW9yZSBwcm9ub3VuY2VkIHdoZW4gY29uc2lkZXJpbmcgQU1MIHNhbXBsZXMgKDY2LjclKS4KCk9ubHkgY2VsbHMgZnJvbSBoZWFsdGh5IGRvbm9ycyBhbmQgZ2Vub3R5cGVkIG1hbGlnbmFudCBjZWxscyBmcm9tIEFNTCBjZWxscyB3ZXJlIHVzZWQgZm9yIHRyYWluaW5nIHRoZSBtYWxpZ25hbnQgdnMuIG5vcm1hbCBjbGFzc2lmaWVyLiAKSS5lLiwgYWxsIGhlYWx0aHkgY2VsbHMgaW4gdGhlIHRyYWluaW5nIGRhdGEgd2VyZSBtYWxlLiAgClRoaXMgd291bGQgdXN1YWxseSByZXN1bHQgaW4gYSBjbGFzc2lmaWNhdGlvbiBvZiBhbGwgZmVtYWxlIGNlbGxzIGFzIEFNTCBjZWxscy4gSG93ZXZlciwgdGhlcmUgaXMgYWxzbyBhIG1hbGUgQU1MIHNhbXBsZXMgd2l0aCBsb3NzIG9mIFkgKEFNTDcwN0IpLCB3aGljaCBtYWtlcyBzZXggbW9yZSBhbWJpZ3VvdXNseSBsaW5rZWQgdG8gbWFsaWduYW50IHN0YXR1cy4gCkZ1cnRoZXJtb3JlLCBleHByZXNzaW9uIG9mIHNleC1zcGVjaWZpYyBnZW5lcyBjb3VsZCBiZSBsb3cvc3BhcnNlLCBhbmQgbm90IGFsbCBjZWxscyBtaWdodCBiZSBkaXN0aW5ndWlzaGFibGUgYnkgc2V4IGJhc2VkIG9uIHRoZWlyIGV4cHJlc3Npb24uCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02fQojIGRvbm9ycwpwMSA8LSBkb25vcl9tZXRhZGF0YSAlPiUKICBzZWxlY3QoU2FtcGxlLCBHZW5kZXIpICU+JQogIG11dGF0ZShTYW1wbGUgPSBnc3ViKCIgLioiLCAiIiwgU2FtcGxlKSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgbXV0YXRlKEdyb3VwID0gaWZlbHNlKGdyZXBsKCJCTSIsIFNhbXBsZSksICJIZWFsdGh5IiwgIkFNTCIpKSAlPiUKICBncm91cF9ieShHcm91cCwgR2VuZGVyKSAlPiUKICBzdW1tYXJpemUobl9kb25vcnMgPSBuKCkpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IEdlbmRlciwgeSA9IG5fZG9ub3JzLCB4ID0gR3JvdXApKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIsCiAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpCiAgKQoKIyBzYW1wbGVzCnAyIDwtIGRvbm9yX21ldGFkYXRhICU+JQogIHNlbGVjdChTYW1wbGUsIGBEYXlzIGZyb20gZGlhZ25vc2lzYCwgR2VuZGVyKSAlPiUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUoR3JvdXAgPSBpZmVsc2UoZ3JlcGwoIkJNIiwgU2FtcGxlKSwgIkhlYWx0aHkiLCAiQU1MIikpICU+JQogIGdyb3VwX2J5KEdyb3VwLCBHZW5kZXIpICU+JQogIHN1bW1hcml6ZShuX3NhbXBsZXMgPSBuKCkpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IEdlbmRlciwgeSA9IG5fc2FtcGxlcywgeCA9IEdyb3VwKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKZ2dwdWJyOjpnZ2FycmFuZ2UocDEsIHAyLCBhbGlnbiA9ICJoIiwgd2lkdGhzID0gYygxLCAxLjMpKQpgYGAKCgojIyMgQ2FuY2VyIGRyaXZlciBtdXRhdGlvbnMKClBsb3Qgd2hpY2ggc2FtcGxlIGhhcyB3aGljaCBjYW5jZXIgZHJpdmVyIGdlbmUgbXV0YXRlZCAoc2VlIEZpZy4gMkIpLiAgCkFNTDcyMkIgQkNPUiBpbiBwYXBlciBGaWcuIDJiIGJ1dCBub3QgaW4gc3VwcGxlbWVudGFyeSBtZXRhZGF0YS4gIApBbGwgc2FtcGxlcyBoYXZlIGEgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIG11dGF0aW9ucy4gCgpgYGB7cn0Kc2FtcGxlX211dGF0aW9uX21hdHJpeCA8LSBkb25vcl9tZXRhZGF0YSAlPiUKICBmaWx0ZXIoCiAgICAhYFJIUCBNdXRhdGlvbnNgICVpbiUgYygiTm90IHBlcmZvcm1lZCIsICJOb25lIERldGVjdGVkIiwgIlVua25vd24iLCAiTkEiKSAmCiAgICAgICFpcy5uYShgUkhQIE11dGF0aW9uc2ApCiAgKSAlPiUKICBtdXRhdGUoZHJpdmVyX211dGF0aW9ucyA9IGBSSFAgTXV0YXRpb25zYCkgJT4lCiAgc2VwYXJhdGVfbG9uZ2VyX2RlbGltKGNvbHMgPSBkcml2ZXJfbXV0YXRpb25zLCAiLy8vICIpICU+JQogIG11dGF0ZShkcml2ZXJfbXV0YXRpb25zID0gZ3N1YigiIC4qIiwgIiIsIGRyaXZlcl9tdXRhdGlvbnMpKSAlPiUKICAjIHVuaXF1ZSBmb3Igc2FtcGxlcyB3aXRoIG11bHRpcGxlIG11dGF0aW9ucyBpbiBzYW1lIGdlbmUKICB1bmlxdWUoKSAlPiUKICAjIGNvdW50IG51bWJlciBvZiBzYW1wbGVzIGZvciBlYWNoIGdlbmUgYW5kIHNvcnQgYnkgdGhhdAogIGdyb3VwX2J5KGRyaXZlcl9tdXRhdGlvbnMpICU+JQogIG11dGF0ZShuX3NhbXBsZXMgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhuX3NhbXBsZXMpKSAlPiUKICBtdXRhdGUodmFsdWUgPSAxKQoKc2FtcGxlX211dGF0aW9uX21hdHJpeCA8LSBzYW1wbGVfbXV0YXRpb25fbWF0cml4ICU+JQogIG11dGF0ZSgKICAgIGRyaXZlcl9tdXRhdGlvbnMgPSBmYWN0b3IoCiAgICAgIGRyaXZlcl9tdXRhdGlvbnMsCiAgICAgIGxldmVscyA9IHVuaXF1ZShzYW1wbGVfbXV0YXRpb25fbWF0cml4JGRyaXZlcl9tdXRhdGlvbnMpLAogICAgICBvcmRlcmVkID0gVAogICAgKSwKICAgIFNhbXBsZSA9IGZhY3RvcigKICAgICAgU2FtcGxlLAogICAgICBsZXZlbCA9IHVuaXF1ZShzYW1wbGVfbXV0YXRpb25fbWF0cml4JFNhbXBsZSksCiAgICAgIG9yZGVyZWQgPSBUCiAgICApCiAgKQoKIyBtdXRhdGlvbnMKcDEgPC0gc2FtcGxlX211dGF0aW9uX21hdHJpeCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkcml2ZXJfbXV0YXRpb25zLCB5ID0gU2FtcGxlKSkgKwogIGdlb21fdGlsZShmaWxsID0gImRhcmtyZWQiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgICBhbmdsZSA9IDkwLAogICAgdmp1c3QgPSAwLjUsCiAgICBoanVzdCA9IDEKICApKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKCiMgcmVhcnJhbmdlbWVudHMKcDIgPC0gc2FtcGxlX211dGF0aW9uX21hdHJpeCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBgQ29tbW9uIHRyYW5zbG9jYXRpb25gLCB5ID0gU2FtcGxlKSkgKwogIGdlb21fdGlsZShmaWxsID0gImRhcmtyZWQiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgICBhbmdsZSA9IDkwLAogICAgdmp1c3QgPSAwLjUsCiAgICBoanVzdCA9IDEKICApKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICB5bGFiKCIiKQoKZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcDEsCiAgcDIsCiAgbmNvbCA9IDIsCiAgbnJvdyA9IDEsCiAgYWxpZ24gPSAiaCIsCiAgd2lkdGhzID0gYyg0LCAxKQopCmBgYAoKPCEtLSBBZGQgZHJpdmVyIG11dGF0aW9ucyBhcyBtZXRhZGF0YSB0byBTZXVyYXQgb2JqZWN0LiAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHNhbXBsZV9hbWxfc3VidHlwZV9sYWJlbCA8LSBzYW1wbGVfbXV0YXRpb25fbWF0cml4ICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KFNhbXBsZSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGFtbF9zdWJ0eXBlID0gcGFzdGUwKGRyaXZlcl9tdXRhdGlvbnMsIGNvbGxhcHNlID0gIiwiKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGFtbF9zdWJ0eXBlID0gZ3N1YigiLE5BIiwgIiIsIHBhc3RlKGFtbF9zdWJ0eXBlLCBgQ29tbW9uIHRyYW5zbG9jYXRpb25gLCBzZXAgPSAiLCIpKSkgJT4lIC0tPgo8IS0tICAgc2VsZWN0KFNhbXBsZSwgYW1sX3N1YnR5cGUpICU+JSAtLT4KPCEtLSAgIHVuaXF1ZSgpIC0tPgoKPCEtLSB0bXAgPC0gLS0+CjwhLS0gICBtZXJnZShyb3duYW1lc190b19jb2x1bW4oZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEpLCAtLT4KPCEtLSAgICAgICAgIHNhbXBsZV9hbWxfc3VidHlwZV9sYWJlbCwgLS0+CjwhLS0gICAgICAgICBieSA9ICJTYW1wbGUiKSAtLT4KPCEtLSB0bXAgPC0gY29sdW1uX3RvX3Jvd25hbWVzKHRtcCwgInJvd25hbWUiKSAtLT4KCjwhLS0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEkRHJpdmVyTXV0YXRpb25zIDwtIC0tPgo8IS0tICAgdG1wW2NvbG5hbWVzKGdhbGVuX2FtbF9kb25vcnMpLF0kYW1sX3N1YnR5cGUgLS0+CjwhLS0gYGBgIC0tPgoKIyMjIENlbGwgdHlwZSBjb21wb3NpdGlvbgoKQ2VsbCB0eXBlcyBwcmVzZW50IGluIGRhdGEuCgpgYGB7cn0KdW5pcXVlKGdhbGVuX2FtbF9kb25vcnMkQ2VsbFR5cGUpCmBgYAoKTmV3IG1ldGFkYXRhIGNvbHVtbiB3aGV0aGVyIGNlbGwgaXMgYSBjYW5jZXIgY2VsbHR5cGUgb3IgaGVhbHRoeS4KCkNvbG9yIHNjaGVtZSB0byBhdm9pZCByYWluYm93IGNvbG9ycy4KCmBgYHtyfQpjZWxsX3R5cGVzIDwtIHVuaXF1ZShnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSRDZWxsVHlwZSkKY2VsbF90eXBlX2NvbG9ycyA8LQogIHNhbXBsZShjaGFtZWxlb246OmRpc3RpbmN0X2NvbG9ycyhuID0gbGVuZ3RoKGNlbGxfdHlwZXMpKSRuYW1lLAogICAgICAgICBzaXplID0gbGVuZ3RoKGNlbGxfdHlwZXMpKQpjZWxsX3R5cGVfY29sb3JzIDwtIHNldE5hbWVzKGNlbGxfdHlwZV9jb2xvcnMsIGNlbGxfdHlwZXMpCmBgYAoKQ3JlYXRlIG1vcmUgImNvYXJzZSIgY2VsbCB0eXBlIGxhYmVscy4gCkx5bXBob2lkIGFuZCBlcnl0aHJvaWQgKG5vbi1tYWxpZ25hbnQgb25seSksIGFuZCBNeWVsb2lkL0hTQywgTXllbG9pZC9IU0MtbGlrZS4gCgpgYGB7cn0KbHltcGhvaWRfY2VsbHR5cGVzIDwtIGMoIlBsYXNtYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJOSyIsCiAgICAgICAgICAgICAgICAgICAgICAgICJQcm9CIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkIiLAogICAgICAgICAgICAgICAgICAgICAgICAiVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJDVEwiLAogICAgICAgICAgICAgICAgICAgICAgICAicERDIikKYGBgCgpgYGB7cn0KZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgPC0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgJT4lCiAgbXV0YXRlKElzQ2FuY2VyQ2VsbHR5cGUgPSBpZmVsc2UoZ3JlcGwoIi1saWtlIiwgQ2VsbFR5cGUpLCBULCBGKSkgJT4lCiAgbXV0YXRlKENlbGxUeXBlR2VuZXJhbCA9IGlmZWxzZSgKICAgIENlbGxUeXBlICVpbiUgbHltcGhvaWRfY2VsbHR5cGVzLAogICAgIkx5bXBob2lkIiwKICAgIGlmZWxzZSgKICAgICAgSXNDYW5jZXJDZWxsdHlwZSwKICAgICAgIk15ZWxvaWQvSFNQQy1saWtlIiwKICAgICAgaWZlbHNlKGdyZXBsKCJFcnkiLCBDZWxsVHlwZSksICJFcnl0aHJvaWQiLCAiTXllbG9pZC9IU1BDIikKICAgICkKICApKQpgYGAKClZpc3VhbGl6ZSBjZWxsIHR5cGUgcHJvcG9ydGlvbnMgYWNyb3NzIHNhbXBsZXMuIAoKCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTh9CmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQpICU+JQogIGNvdW50KENlbGxUeXBlKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBDZWxsVHlwZSwgeSA9IG4sIHggPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiLCBzdGF0ID0gImlkZW50aXR5IikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNlbGxfdHlwZV9jb2xvcnNbdW5pcXVlKGdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhJENlbGxUeXBlKV0pICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKCmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQpICU+JQogIGNvdW50KElzQ2FuY2VyQ2VsbHR5cGUpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IElzQ2FuY2VyQ2VsbHR5cGUsIHkgPSBuLCB4ID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKCmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQpICU+JQogIGNvdW50KFByZWRpY3Rpb25SZWZpbmVkKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBQcmVkaWN0aW9uUmVmaW5lZCwgeSA9IG4sIHggPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiLCBzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQoKZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkob3JpZy5pZGVudCkgJT4lCiAgY291bnQoQ2VsbFR5cGVHZW5lcmFsKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBDZWxsVHlwZUdlbmVyYWwsIHkgPSBuLCB4ID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpNeWVsb2lkIGFuZCBIU1BDcyBhcmUgdGhlIGNlbGxzIHRoYXQgYXJlIGRpZmZpY3VsdCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIGhlYWx0aHkgYW5kIG1hbGlnbmFudCBpbiBBTUwgY2VsbHMuIApUaGlzIGlzIHRoZSBhY3R1YWwgZGlmZmljdWx0IHByZWRpY3Rpb24gdGFzayBvZiB0aGUgY2xhc3NpZmllci4gClZpc3VhbGl6ZSB0aGUgcHJvcG9ydGlvbiBvZiB0aGVzZSBjZWxscyBpbiB0aGUgc2FtcGxlcy4gCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD04fQpnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoQ2VsbFR5cGVHZW5lcmFsICVpbiUgYygiTXllbG9pZC9IU1BDIiwgIk15ZWxvaWQvSFNQQy1saWtlIikpICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQsIENlbGxUeXBlR2VuZXJhbCkgJT4lCiAgc3VtbWFyaXplKG5fY2VsbHMgPSBuKCkpICU+JQogIGdncGxvdChhZXMoeCA9IG9yaWcuaWRlbnQsIHkgPSBuX2NlbGxzLCBmaWxsID0gQ2VsbFR5cGVHZW5lcmFsKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJmaWxsIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClByb3BvcnRpb24gb2YgY3ljbGluZyBjZWxscyBpcyBub3QgaGlnaGVyIGluIEFNTCBzYW1wbGVzIGF0IGRpYWdub3NpcyB0aGFuIGluIGhlYWx0aHkgc2FtcGxlcy4KQU1MIGlzIGEgZGlzZWFzZSBvZiBkaWZmZXJlbnRpYXRpb24uIApUaGUgYWNjdW11bGF0aW9uIG9mIGJsYXN0cyBpbiB0aGUgYm9uZSBtYXJyb3cgaXMgdGhlIHByb2JsZW0sIG5vdCBoeXBlciBwcm9saWZlcmF0aW9uLCBhcyBpbiBvdGhlciBjYW5jZXJzLiAKCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTh9CmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGZpbHRlcihgRGF5cyBmcm9tIGRpYWdub3Npc2AgPT0gIkQwIiB8CiAgICAgICAgICAgU2FtcGxlQ2xhc3MgPT0gIkhlYWx0aHlEb25vciIpICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQsIFNhbXBsZUNsYXNzKSAlPiUKICBjb3VudChDeWNsaW5nQmluYXJ5KSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBDeWNsaW5nQmluYXJ5LCB5ID0gbiwgeCA9IG9yaWcuaWRlbnQpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIsIHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgZmFjZXRfZ3JpZCggfiBTYW1wbGVDbGFzcywgc2NhbGVzID0gImZyZWVfeCIsIHNwYWNlID0gImZyZWUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCgojIyMgR2Vub3R5cGUgaW5mb3JtYXRpb24KCkNsYXNzaWZ5IGNlbGxzIGJhc2VkIG9uIHRoZWlyIGdlbm90eXBlIGluZm9ybWF0aW9uIGludG8gbWFsaWduYW50IGFuZCBoZWFsdGh5IGNlbGxzLiAKCkkgY2xhc3NpZnkgYWxsIGNlbGxzIHdpdGggYSBtdXRhbnQgdHJhbnNjcmlwdCBkZXRlY3RlZCBhcyBtYWxpZ25hbnQgY2VsbHMuICAKQWxsIG90aGVyIGNlbGxzIGZyb20gQU1MIHNhbXBsZXMgd2l0aCBhIHd0IGFsbGVsZSBkZXRlY3RlZCBjb3VsZCBiZSBjbGFzc2lmaWVkIGFzIGhlYWx0aHkuICAKSG93ZXZlciwgdGhpcyBpcyBpbXBlcmZlY3QsIGVzcGVjaWFsbHkgZm9yIGhldGVyb3p5Z291cyBkb21pbmFudCBuZWdhdGl2ZSBtdXRhdGlvbnMuICAKRnVydGhlcm1vcmUsIGR1ZSB0byBjbG9uYWxpdHksIHRoZXJlIG1pZ2h0IGJlIGNlbGxzIHdpdGggd3QgYWxsZWxlIGRldGVjdGVkIGFuZCBtdXQgYWxsZWxlIG1pc3NlZC4gIApCb3RoIGNhbiBsZWFkIHRvIGZhbHNlIHBvc2l0aXZlIGhlYWx0aHkgY2VsbHMuIAoKVGhlcmVmb3JlLCBvbmx5IHVzaW5nIGNlbGxzIGZyb20gQU1MIGRvbm9ycyBnZW5vdHlwZWQgYXMgbXV0YW50IGFuZCBoZWFsdGh5IGNlbGxzIGZyb20gaGVhbHRoeSBkb25vcnMgaW4gdGhlIGNsYXNzaWZpZXIgKGFzIGRvbmUgYnkgdmFuIEdhbGVuKS4KCiJUaGUgc2Vjb25kIGNsYXNzaWZpZXIgaXMgdXNlZCBmb3IgZGV0ZXJtaW5pbmcgaWYgYSBjZWxsIGZvciB3aGljaCB3ZSBkaWQgbm90IGRldGVjdCBhIG11dGFudCB0cmFuc2NyaXB0IGlzIG1hbGlnbmFudCBvciBub3JtYWwsIGJhc2VkIG9uIGl0cyBzaW1pbGFyaXR5IHRvIG5vcm1hbCBhbmQgbWFsaWduYW50IGNlbGxzIChpLmUuLCBjZWxscyBmcm9tIGhlYWx0aHkgQk0gYW5kIEhTQyB0byBteWVsb2lkLWxpa2UgY2VsbHMgZnJvbSB0dW1vciBzYW1wbGVzIGZvciB3aGljaCB3ZSBkZXRlY3RlZCBtdXRhbnQgdHJhbnNjcmlwdHMpLiIKCmBgYHtyfQpnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBtdXRhdGUoZ2Vub3R5cGUgPSBpZmVsc2UoCiAgICAhaXMubmEoTXV0VHJhbnNjcmlwdHMpICYKICAgICAgTXV0VHJhbnNjcmlwdHMgIT0gIiIsCiAgICAibWFsaWduYW50IiwKICAgIGlmZWxzZSgKICAgICAgIWlzLm5hKFd0VHJhbnNjcmlwdHMpICYKICAgICAgICBXdFRyYW5zY3JpcHRzICE9ICIiLAogICAgICAiaGVhbHRoeSIsCiAgICAgICJub3RfZGV0ZWN0ZWQiCiAgICApCiAgKSkKYGBgCgpQbG90IDE6IENlbGxzIHdpdGggbXV0IGRldGVjdGVkLCBvbmx5IHd0IGRldGVjdGVkIG9yIG5vIGNvdmVyYWdlLCBjb21wYXJlZCB0byBleHBlY3RlZCBibGFzdCBjb3VudC4KClBsb3QgMjogQ2VsbHMgdXNhYmxlIHRvIHRyaWFuIG1hbGlnbmFudCB2cy4gaGVhbHRoeSBjbGFzc2lmaWVyLiAKClRoZXJlIGFyZSBtdWx0aXBsZSBzYW1wbGVzIGFuZCBwYXRpZW50cyB3aXRoIG5vIG9yIGFsbW9zdCBubyBtYWxpZ25hbnQgZ2Vub3R5cGVkIGNlbGxzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIHRyYWluaW5nLiAgClRoZXJlZm9yZSwgdGhlIGNsYXNzaWZpZXIgbmVlZHMgdG8gYmUgZ2VuZXJhbGl6YWJsZSB0byBvdGhlciBzYW1wbGVzIGFuZCBtdXRhdGlvbnMuICAKT3ZlcmZpdHRpbmcgdG8gc3BlY2lmaWMgcGF0aWVudHMgd291bGQgYmUgYmFkLgoKYGBge3J9CnAxIDwtIGdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQsIGdlbm90eXBlKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IGdlbm90eXBlLCB5ID0gbl9jZWxscywgeCA9IG9yaWcuaWRlbnQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsKICB4bGFiKGVsZW1lbnRfYmxhbmsoKSkKCnAyIDwtIGdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIHNlbGVjdChgQmxhc3QgY291bnRgLCBvcmlnLmlkZW50KSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBgQmxhc3QgY291bnRgLCB4ID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHNjYWxlX3hfZGlzY3JldGUoZHJvcCA9IEZBTFNFKSArCiAgeGxhYihlbGVtZW50X2JsYW5rKCkpCgpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwMSwKICBwMiwKICBhbGlnbiA9ICJ2IiwKICBoZWlnaHRzID0gYygyLCAxKSwKICBuY29sID0gMSwKICBucm93ID0gMgopCgpwMSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBtdXRhdGUoU2FtcGxlID0gYXMuZmFjdG9yKFNhbXBsZSkpICU+JQogIGZpbHRlcihnZW5vdHlwZSA9PSAibWFsaWduYW50IiB8CiAgICAgICAgICAgU2FtcGxlQ2xhc3MgPT0gIkhlYWx0aHlEb25vciIpICU+JQogIGdyb3VwX2J5KFNhbXBsZSwgZ2Vub3R5cGUpICU+JQogIHN1bW1hcmlzZShuX2NlbGxzID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KGFlcyhmaWxsID0gZ2Vub3R5cGUsIHkgPSBuX2NlbGxzLCB4ID0gU2FtcGxlKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpCgpwMiA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgfAogICAgICAgICAgIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSAlPiUKICBncm91cF9ieShvcmlnLmlkZW50LCBnZW5vdHlwZSkgJT4lCiAgc3VtbWFyaXNlKG5fY2VsbHMgPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBnZW5vdHlwZSwgeSA9IG5fY2VsbHMsIHggPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpCgpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwMSwKICBwMiwKICBhbGlnbiA9ICJ2IiwKICBoZWlnaHRzID0gYygxLCAxKSwKICBuY29sID0gMSwKICBucm93ID0gMgopCmBgYAoKQ29uY29yZGFuY2Ugb2YgZ2Vub3R5cGUgYW5kIHZhbiBHYWxlbiBjZWxsdHlwZSBhbm5vdGF0aW9uIGluIEFNTCBzYW1wbGVzLiAKTkI6IENlbGxzIHdpdGggb25seSB3dCB0cmFuc2NyaXB0IGRldGVjdGVkICgiaGVhbHRoeSIpIGluIEFNTCBzYW1wbGVzIG1pZ2h0IG5vdCBiZSBhY3R1YWxseSBoZWFsdGh5IChoZXRlcm96eWdvc2l0eSwgY2xvbmFsaXR5KS4gCgpgYGB7cn0KZ2FsZW5fYW1sX2Rvbm9yc19tZXRhZGF0YSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YQp0YWJsZSgKICAgIGZpbHRlcihnYWxlbl9hbWxfZG9ub3JzX21ldGFkYXRhLCBTYW1wbGVDbGFzcyAhPSAiSGVhbHRoeURvbm9yIikkZ2Vub3R5cGUsCiAgICBmaWx0ZXIoZ2FsZW5fYW1sX2Rvbm9yc19tZXRhZGF0YSwgU2FtcGxlQ2xhc3MgIT0gIkhlYWx0aHlEb25vciIpJENlbGxUeXBlCiAgKQpgYGAKCgo8IS0tIEhlYXRtYXAgb2YgbnVtYmVyIG9mIGNlbGxzIGZyb20gQU1MIGRvbm9ycyAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdhbGVuX2FtbF9kb25vcnNfbWV0YWRhdGEgPC0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgLS0+CjwhLS0gaGVhdG1hcCggLS0+CjwhLS0gICB0YWJsZSggLS0+CjwhLS0gICAgIGZpbHRlcihnYWxlbl9hbWxfZG9ub3JzX21ldGFkYXRhLCBTYW1wbGVDbGFzcyAhPSAiSGVhbHRoeURvbm9yIikkZ2Vub3R5cGUsIC0tPgo8IS0tICAgICBmaWx0ZXIoZ2FsZW5fYW1sX2Rvbm9yc19tZXRhZGF0YSwgU2FtcGxlQ2xhc3MgIT0gIkhlYWx0aHlEb25vciIpJENlbGxUeXBlIC0tPgo8IS0tICAgKSwgLS0+CjwhLS0gICBzY2FsZSA9IE5VTEwsIC0tPgo8IS0tICAgQ29sdiA9IE5BLCAtLT4KPCEtLSAgIFJvd3YgPSBOQSAtLT4KPCEtLSApIC0tPgo8IS0tIGBgYCAtLT4KCgoKU2V4IGJpYXMgaW4gY2VsbHMgdGhhdCBjYW4gYmUgdXNlZCB0byB0cmFpbiBjbGFzc2lmaWVyIChtdXQgdHJhbnNjcmlwdCBkZXRlY3RlZCBmcm9tIEFNTCBkb25vciwgb3IgZnJvbSBoZWFsdGh5IGRvbm9yKS4gCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02fQpwMSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgfAogICAgICAgICAgIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSAlPiUKICBtdXRhdGUoY2xhc3MgPSBpZmVsc2UoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIsICJtYWxpZ25hbnQiLCAiaGVhbHRoeSIpKSAlPiUKICBncm91cF9ieShHZW5kZXIsIGNsYXNzKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoeCA9IGNsYXNzLCB5ID0gbl9jZWxscywgZmlsbCA9IEdlbmRlcikpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICAjIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCgpwMiA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgfAogICAgICAgICAgIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSAlPiUKICBtdXRhdGUoY2xhc3MgPSBpZmVsc2UoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIsICJtYWxpZ25hbnQiLCAiaGVhbHRoeSIpKSAlPiUKICBncm91cF9ieShHZW5kZXIsIGNsYXNzKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoeCA9IEdlbmRlciwgeSA9IG5fY2VsbHMsIGZpbGwgPSBjbGFzcykpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICAjIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCgpwMSB8IHAyCmBgYAoKCiMjIyBBY2N1cmFjeSBvZiB0aGUgY2xhc3NpZmllcgoKQ2hlY2sgdGhlIGFjY3VyYWN5IG9mIHRoZSB2YW4gR2FsZW4gY2xhc3NpZmllciBiYXNlZCBvbiBjb25jb3JkYW5jZSB3aXRoIGNsaW5pY2FsIGJsYXN0IGNvdW50LiAKVGhpcyBoYXMgYWxyZWFkeSBiZWVuIGNoZWNrZWQgaW4gdGhlIHBhcGVyIGFuZCB0aGUgYW5zd2VyIGlzIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGlzIGhpZ2guIAoKYGBge3J9CiMgc2FtcGxlcyB3aXRoIHVuY2xlYXIgcHJlZGljdGlvbgpnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoUHJlZGljdGlvblJlZmluZWQgPT0gInVuY2xlYXIiKSAlPiUKICBwdWxsKG9yaWcuaWRlbnQpICU+JQogIHVuaXF1ZSgpICU+JSBhcy5jaGFyYWN0ZXIoKQoKcHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnQgPC0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkoCiAgICBvcmlnLmlkZW50LAogICAgUHJlZGljdGlvblJlZmluZWQsCiAgICBgQmxhc3QgY291bnRgLAogICAgYENlbGwgbnVtYmVyYCwKICAgIFNhbXBsZSwKICAgIEdlbmRlciwKICAgICMgRHJpdmVyTXV0YXRpb25zLAogICAgYERheXMgZnJvbSBkaWFnbm9zaXNgCiAgKSAlPiUKICBzdW1tYXJpemUoUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uID0gbigpIC8gYENlbGwgbnVtYmVyYCkgJT4lCiAgZmlsdGVyKFByZWRpY3Rpb25SZWZpbmVkID09ICJtYWxpZ25hbnQiKSAlPiUKICBzZWxlY3QoCiAgICBgQmxhc3QgY291bnRgLAogICAgUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uLAogICAgb3JpZy5pZGVudCwKICAgIFNhbXBsZSwKICAgIEdlbmRlciwKICAgICMgRHJpdmVyTXV0YXRpb25zLAogICAgYERheXMgZnJvbSBkaWFnbm9zaXNgCiAgKSAlPiUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUoYEJsYXN0IGNvdW50YCA9IGFzLm51bWVyaWMoYEJsYXN0IGNvdW50YCkpICU+JQogIHVuZ3JvdXAoKQoKbnJvdyhwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudCkKCnByZWRfbWFsaWduYW50X3ZzX2JsYXN0X2NvdW50ICU+JQogIGdncGxvdChhZXMoCiAgICB4ID0gUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uLAogICAgeSA9IGBCbGFzdCBjb3VudGAsCiAgICBncm91cCA9IFNhbXBsZSwKICAgIGNvbG9yID0gU2FtcGxlCiAgKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSArCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbCA9IG9yaWcuaWRlbnQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4Lm92ZXJsYXBzID0gSW5mKSArCiAgdGhlbWVfYncoKSArCiAgY29vcmRfZXF1YWwoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgoKCmBgYHtyfQpjb3IudGVzdCgKICBwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudCRQcmVkaWN0aW9uUmVmaW5lZFByb3BvcnRpb24sCiAgcHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnQkYEJsYXN0IGNvdW50YAopCmBgYAoKSWYgdGhlIHNleCBiaWFzIGluIGhlYWx0aHkgYW5kIEFNTCBzYW1wbGVzIG1hdHRlcnMsIHRoZW4gdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIG9ic2VydmVkIGJsYXN0IGNvdW50IChlcnJvcikgc2hvdWxkIGJlIGxhcmdlciBpbiBmZW1hbGVzIHRoYW4gbWFsZXMuIAoKUHJlZGljdGVkIHByb3BvcnRpb24gb2YgbWFsaWduYW50IGNlbGxzIGFncmVlcyBsZXNzIHdpdGggY2xpbmljYWwgYmxhc3QgY291bnQgZm9yIGZlbWFsZSBzYW1wbGVzLgpNYXJrZWRseSwgZmVtYWxlIHNhbXBsZXMgaGF2ZSBhbiBvdmVyZXN0aW1hdGVkIHByb3BvcnRpb24gb2YgbWFsaWduYW50IGNlbGwuIAoKSG93ZXZlciwgdGhpcyBjb3VsZCBhbHNvIGJlIGR1ZSB0byBvdGhlciBmYWN0b3JzIGNvdmFyeWluZyB3aXRoIFNleCwgbGlrZSBtdXRhdGVkIGdlbmUgb3IgYmxhc3QgY291bnQuCgpNZWRpYW4gYWJzb2x1dGUgZXJyb3IgMTAuOCUoRiksIDMuMSUoTSkuCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD00fQpwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudF9lcnJvciA8LSBwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudCAlPiUKICBtdXRhdGUoZXJyb3IgPSBQcmVkaWN0aW9uUmVmaW5lZFByb3BvcnRpb24gLSBgQmxhc3QgY291bnRgLAogICAgICAgICBhYnNvbHV0ZV9lcnJvciA9IGFicyhlcnJvciksCiAgICAgICAgIHJlbGF0aXZlX2Vycm9yID0gUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uIC8gYEJsYXN0IGNvdW50YCkgJT4lCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IGMoZXJyb3IsIGFic29sdXRlX2Vycm9yLCByZWxhdGl2ZV9lcnJvciksCiAgICBuYW1lc190byA9ICJlcnJvcl90eXBlIiwKICAgIHZhbHVlc190byA9ICJlcnJvciIKICApICU+JQogIGdyb3VwX2J5KGVycm9yX3R5cGUsIEdlbmRlcikgCgpwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudF9lcnJvciAlPiUKICBzdW1tYXJpemUobWVkaWFuX2Vycm9yID0gcm91bmQobWVkaWFuKGVycm9yKSwgMykpCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTR9CnN5bW1ldHJpY19saW1pdHMgPC0gZnVuY3Rpb24gKHgpCnsKICBtYXggPC0gbWF4KGFicyh4KSkKICBjKC1tYXgsIG1heCkKfQoKcDEgPC0gcHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnRfZXJyb3IgJT4lCiAgZmlsdGVyKGVycm9yX3R5cGUgJWluJSBjKCJlcnJvciIsICJhYnNvbHV0ZV9lcnJvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBlcnJvcl90eXBlLCB5ID0gZXJyb3IgKiAxMDAsIGZpbGwgPSBHZW5kZXIpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDApICsKICBnZW9tX3BvaW50KHBjaCA9IDIxLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKGppdHRlci53aWR0aCA9IDAuMSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChzY2FsZSA9IDEpKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gIk5vbmUiKSArCiAgeWxhYigiUHJlZGljdGlvbiBlcnJvciAlIGJsYXN0cyIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gc3ltbWV0cmljX2xpbWl0cykgKwogIHhsYWIoZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKCnAyIDwtIHByZWRfbWFsaWduYW50X3ZzX2JsYXN0X2NvdW50X2Vycm9yICU+JQogIGZpbHRlcihlcnJvcl90eXBlICVpbiUgYygicmVsYXRpdmVfZXJyb3IiKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZXJyb3JfdHlwZSwgeSA9IGxvZzIoZXJyb3IpLCBmaWxsID0gR2VuZGVyKSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwKSArCiAgZ2VvbV9wb2ludChwY2ggPSAyMSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZShqaXR0ZXIud2lkdGggPSAwLjEpKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHlsYWIoImxvZzIgcmVsYXRpdmUgZXJyb3IgJSBibGFzdHMiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IHN5bW1ldHJpY19saW1pdHMpICsKICB4bGFiKGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgpnZ3B1YnI6OmdnYXJyYW5nZShwMSwgcDIsIGFsaWduID0gImgiLCB3aWR0aHMgPSBjKDEsIDEuMikpCmBgYAoKCjwhLS0gUmVzaWR1YWxzIG9mIGxpbmVhciBtb2RlbC4gIC0tPgoKPCEtLSBgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0zLjV9IC0tPgo8IS0tIG1vZGVsIDwtIGxtKFByZWRpY3Rpb25SZWZpbmVkUHJvcG9ydGlvbiB+IGBCbGFzdCBjb3VudGAsIGRhdGE9cHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnQpICAtLT4KCjwhLS0gdG1wIDwtIHByZWRfbWFsaWduYW50X3ZzX2JsYXN0X2NvdW50ICU+JSAtLT4KPCEtLSAgIGRwbHlyOjptdXRhdGUoIC0tPgo8IS0tICAgICBmaXRzID0gZml0dGVkKG1vZGVsKSwgLS0+CjwhLS0gICAgIHJlc2lkcyA9IHJlc2lkKG1vZGVsKSwgLS0+CjwhLS0gICAgIHNyZXNpZHMgPSByc3R1ZGVudChtb2RlbCkgLS0+CjwhLS0gICApIC0tPgoKPCEtLSB0bXAgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoR2VuZGVyKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpemUoc3VtKGFicyhyZXNpZHMpKSwgbWVhbihhYnMocmVzaWRzKSksIG1lZGlhbihhYnMocmVzaWRzKSkpIC0tPgoKPCEtLSAjY3JlYXRlIHJlc2lkdWFsIHBsb3QgLS0+CjwhLS0gZ2dwbG90KHRtcCwgYWVzKHggPSBmaXRzLCB5ID0gcmVzaWRzLCBjb2xvciA9IEdlbmRlcikpICsgLS0+CjwhLS0gICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsID0gcGFzdGUoU2FtcGxlLCAsIHNlcCA9ICJ8IikpLCBtYXgub3ZlcmxhcHMgPSBJbmYpICsgLS0+CjwhLS0gICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9IDIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KCkgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KCkgLS0+CjwhLS0gYGBgIC0tPgoKCgojIyMgRGF0YSBxdWFsaXR5CgpBY2NvcmRpbmcgdG8gbWFudXNjcmlwdCwgY2VsbHMgd2VyZSBmaWx0ZXJlZCBmb3IgPjEwMDAgVU1JLCA+NTAwIGdlbmVzIGFuZCA8MjAlIG1pdG9jaG9uZHJpYWwrcmlib3NvbWFsIHRyYW5zY3JpcHRzLgoKVGhlIFJkYXRhIG9iamVjdCBjb250YWlucyBmaWx0ZXJlZCBjZWxscy4gCgpgYGB7ciBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9MTB9ClZsblBsb3QoZ2FsZW5fYW1sX2Rvbm9ycywgZmVhdHVyZXMgPSBjKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIpLCBuY29sID0gMSwgbG9nID0gVCwgcHQuc2l6ZSA9IDApCmBgYAoKPCEtLSBgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD01fSAtLT4KPCEtLSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShvcmlnLmlkZW50LCBgQ2VsbCBudW1iZXJgLCBTYW1wbGVDbGFzcykgJT4lIC0tPgo8IS0tICAgZHBseXI6OnN1bW1hcmlzZShtZWRpYW5fbkZlYXR1cmVfUk5BID0gbWVkaWFuKG5GZWF0dXJlX1JOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoeCA9IGBDZWxsIG51bWJlcmAsIHkgPSBtZWRpYW5fbkZlYXR1cmVfUk5BKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gU2FtcGxlQ2xhc3MpKSArIC0tPgo8IS0tICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KCkgKyAtLT4KPCEtLSAgIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpIC0tPgo8IS0tIGBgYCAtLT4KCgojIyMgTG93IGRpbWVuc2lvbmFsIGVtYmVkZGluZwoKIyMjIyBBTUwgYW5kIGhlYWx0aHkgZG9ub3JzCgpOb3JtYWxpemUgZGF0YSBieSBsaWJyYXJ5IHNpemUgYW5kIGxvZyB0cmFuc2Zvcm0uICAKSWRlbnRpZnkgMjAwMCBoaWdobHkgdmFyaWFibGUgZ2VuZXMuICAKU2NhbGUvei1zY29yZSBoaWdobHkgdmFyaWFibGUgZ2VuZXMuICAKCmBgYHtyIHJlc3VsdHM9Rn0KZ2FsZW5fYW1sX2Rvbm9ycyA8LSBOb3JtYWxpemVEYXRhKGdhbGVuX2FtbF9kb25vcnMsIHNjYWxlLmZhY3RvciA9IDEwMDAwKQpnYWxlbl9hbWxfZG9ub3JzIDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnMpCmdhbGVuX2FtbF9kb25vcnMgPC0gU2NhbGVEYXRhKGdhbGVuX2FtbF9kb25vcnMpCmBgYAoKQ2FsY3VsYXRlIFBDQSBhbmQgY2hlY2sgZWxib3cgcGxvdCBmb3IgYXBwcm9wcmlhdGUgbnVtYmVyIG9mIFBDcyBmb3IgVU1BUCBlbWJlZGRpbmcuIAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzIDwtIFJ1blBDQShnYWxlbl9hbWxfZG9ub3JzKQpgYGAKCgpgYGB7cn0KRWxib3dQbG90KGdhbGVuX2FtbF9kb25vcnMsIG5kaW1zID0gNTApICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgTkEpKQpgYGAKCkNhbGN1bGF0ZSBVTUFQIGVtYmVkZGluZy4gCgpgYGB7ciByZXN1bHRzPUZ9CmdhbGVuX2FtbF9kb25vcnMgPC0gUnVuVU1BUChnYWxlbl9hbWxfZG9ub3JzLCBkaW1zID0gMToxNSkKYGBgCgpDaGVjayBVTUFQIGZvciB0ZWNobmljYWwgYW5kIGJpb2xvZ2ljYWwgdmFyaWF0aW9uLgoKSGlnaGVyIHJlYWQgY291bnRzIGluIGVyeXRocm9jeXRlIHByb2dlbml0b3JzLgoKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTd9CiMgdGVjaG5pY2FsCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnMsIGZlYXR1cmVzID0gIm5Db3VudF9STkEiLCBwdC5zaXplID0gMC4wMDUsIG1heC5jdXRvZmYgPSAicTk5IikgKyBjb29yZF9lcXVhbCgpCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnMsIGZlYXR1cmVzID0gIm5GZWF0dXJlX1JOQSIsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTd9CiMgYW5ub3RhdGlvbiBiaW9sb2dpY2FsCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9ycywgZ3JvdXAuYnkgPSAiU2FtcGxlQ2xhc3MiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1KSArIGNvb3JkX2VxdWFsKCkKRGltUGxvdChnYWxlbl9hbWxfZG9ub3JzLCBncm91cC5ieSA9ICJJc0NhbmNlckNlbGx0eXBlIiwgc2h1ZmZsZSA9IFQsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9ycywgZ3JvdXAuYnkgPSAiUHJlZGljdGlvblJlZmluZWQiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1KSArIGNvb3JkX2VxdWFsKCkKRGltUGxvdChnYWxlbl9hbWxfZG9ub3JzLCBncm91cC5ieSA9ICJDZWxsVHlwZSIsIHNodWZmbGUgPSBULCBwdC5zaXplID0gMC4wMDUsIGxhYmVsID0gVCwgbGFiZWwuc2l6ZSA9IDMpICsgY29vcmRfZXF1YWwoKQpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnMsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1LCBsYWJlbCA9IFQsIGxhYmVsLnNpemUgPSAyKSArIGNvb3JkX2VxdWFsKCkgKyBOb0xlZ2VuZCgpCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnMsIGZlYXR1cmVzID0gIkFnZSIsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3Qoc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIsIHNodWZmbGUgPSBULCBwdC5zaXplID0gMC4wMDUpICsgY29vcmRfZXF1YWwoKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD03fQojIHNleCBkaWZmZXJlbmNlcwpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnMsIGdyb3VwLmJ5ID0gIkdlbmRlciIsIHNodWZmbGUgPSBULCBwdC5zaXplID0gMC4wMDUpICsgY29vcmRfZXF1YWwoKQpEaW1QbG90KHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBTYW1wbGVDbGFzcyA9PSAiQW1sRG9ub3IiKSwgZ3JvdXAuYnkgPSAiR2VuZGVyIiwgc2h1ZmZsZSA9IFQsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3Qoc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJBbWxEb25vciIpLCBncm91cC5ieSA9ICJTYW1wbGUiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1LCBzcGxpdC5ieSA9ICJHZW5kZXIiKSArIGNvb3JkX2VxdWFsKCkKRGltUGxvdChnYWxlbl9hbWxfZG9ub3JzLCBncm91cC5ieSA9ICJTYW1wbGUiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1LCBzcGxpdC5ieSA9ICJHZW5kZXIiKSArIGNvb3JkX2VxdWFsKCkKYGBgCgojIyMjIEVtYmVkZGluZyBoZWFsdGh5IGRvbm9ycwoKQ3JlYXRlIGFuIGVtYmVkZGluZyBvZiBqdXN0IHRoZSBoZWFsdGh5IHNhbXBsZXMgdG8gdmlzdWFsaXplIHRoZSBub3JtYWwgaGVtYXRvcG9pZXRpYyB0cmFqZWN0b3J5LiAgClNhbWUgcHJvY2VkdXJlIGFzIGFib3ZlLiAKCmBgYHtyfQpnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkgPC0gc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKQpgYGAKCmBgYHtyIHJlc3VsdHM9Rn0KZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSkKZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5IDwtIFNjYWxlRGF0YShnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkpCmBgYAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkgPC0gUnVuUENBKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSkKYGBgCgpgYGB7cn0KRWxib3dQbG90KGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSwgbmRpbXMgPSA1MCkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCBOQSkpCmBgYAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkgPC0gUnVuVU1BUChnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHksIGRpbXMgPSAxOjE1KQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD03fQpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSwgZ3JvdXAuYnkgPSAiQ2VsbFR5cGUiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDEsIGxhYmVsID0gVCwgbGFiZWwuc2l6ZSA9IDMpICsKICBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5LCBncm91cC5ieSA9ICJvcmlnLmlkZW50Iiwgc2h1ZmZsZSA9IFQsIHB0LnNpemUgPSAwLjAxLCBsYWJlbCA9IFQsIGxhYmVsLnNpemUgPSAzKSArCiAgY29vcmRfZXF1YWwoKSArIE5vTGVnZW5kKCkKYGBgCgoKPCEtLSBDaGVjayBob3cgcmVsaWFibGUgVU1BUCBpcywgaS5lLiBob3cgd2VsbCBpdCBjYXB0dXJlcyB0aGUgUEMgc3BhY2UsIHVzaW5nIHRoZSBkZWZhdWx0IFVNQVAgcGFyYW1ldGVycy4gLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KHNjREVFRCkgLS0+CjwhLS0gSyA9IDggLS0+CjwhLS0gcmVzdWx0ID0gc2NERUVEKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSwgSyA9IEssIHJlZHVjdGlvbi5tZXRob2QgPSAndW1hcCcsIG5fbmVpZ2hib3JzID0gMzAsIG1pbi5kaXN0ID0gMC4zLCByZXJ1biA9IEYpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGR1YmlvdXNfY2VsbHMgPSByZXN1bHQkZnVsbF9yZXN1bHRzJGR1YmlvdXNfY2VsbHNbcmVzdWx0JGZ1bGxfcmVzdWx0cyRuX25laWdoYm9ycyA9PSAnMzAnXSAtLT4KPCEtLSBkdWJpb3VzX2NlbGxzID0gYXMubnVtZXJpYyhzdHJzcGxpdChkdWJpb3VzX2NlbGxzLCAnLCcpW1sxXV0pIC0tPgo8IS0tIHRydXN0d29ydGh5X2NlbGxzID0gIHJlc3VsdCRmdWxsX3Jlc3VsdHMkdHJ1c3R3b3J0aHlfY2VsbHNbcmVzdWx0JGZ1bGxfcmVzdWx0cyRuX25laWdoYm9ycyA9PSAnMzAnXSAtLT4KPCEtLSB0cnVzdHdvcnRoeV9jZWxscyA9IGFzLm51bWVyaWMoc3Ryc3BsaXQodHJ1c3R3b3J0aHlfY2VsbHMsICcsJylbWzFdXSkgLS0+Cgo8IS0tIERpbVBsb3QoIC0tPgo8IS0tICAgZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5LCAtLT4KPCEtLSAgIHJlZHVjdGlvbiA9ICd1bWFwJywgLS0+CjwhLS0gICBjZWxscy5oaWdobGlnaHQgPSBsaXN0KCdkdWJpb3VzJyA9IGR1YmlvdXNfY2VsbHMsICd0cnVzdHdvcnRoeScgPSB0cnVzdHdvcnRoeV9jZWxscykgLS0+CjwhLS0gKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdncmF5JywgJ2JsdWUnLCAncmVkJykpICsgY29vcmRfZXF1YWwoKSAtLT4KPCEtLSBgYGAgLS0+CgojIyMjIEFNTCBhbmQgaGVhbHRoeSBkb25vcnMgZXhjbHVkaW5nIHNleC1zcGVjaWZpYyBnZW5lcwoKU0RFIHNjb3JlcyBmb3IgYWxsIHByb3RlaW4tY29kaW5nIGdlbmVzIGluIDQ1IHRpc3N1ZXMgY29tbW9uIHRvIG1lbiBhbmQgd29tZW4uIApHZW5lcyB3ZXJlIGFuYWx5emVkIGJ5IE5PSVNlcUJJTyB3aXRoIHNjb3JlcyBvZiB6ZXJvIGdpdmVuIGZvciBnZW5lcyB3aXRoIGluc2lnbmlmaWNhbnQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIApPdGhlciBnZW5lcyBoYXZlIFNERSBzY29yZXMgYmVsb3cgemVybyBmb3IgbWVuLWJpYXNlZCBleHByZXNzaW9uIGFuZCBhYm92ZSB6ZXJvIGZvciB3b21lbi1iaWFzZWQgZXhwcmVzc2lvbi4gKENTViAyMjA1IGtiKQoKQm9uZSBtYXJyb3cgaXMgbm90IGF2YWlsYWJsZS4gSW5zdGVhZCB1c2Ugd2hvbGUgYmxvb2QgYW5kIHNwbGVlbi4KCmBgYHtyfQpzZXhfZ2VuZXMgPC0gcmVhZF9jc3YoImdlcnNob25pX2V0X2FsLzEyOTE1XzIwMTdfMzUyX01PRVNNM19FU00uY3N2IikKc2V4X2dlbmVzIDwtIHNleF9nZW5lcyAlPiUgCiAgc2VsZWN0KEdlbmUsIFNwbGVlbiwgV2hvbGVfQmxvb2QpICU+JQogIGZpbHRlcihTcGxlZW4gIT0gMCB8IFdob2xlX0Jsb29kICE9IDApICU+JQogIG11dGF0ZShnZW5lID0gZ3N1YigiXy4qIiwgIiIsIEdlbmUpKSAlPiUKICBwdWxsKGdlbmUpCgpzZXhfZ2VuZXMgPC0gYyhzZXhfZ2VuZXMsICJYSVNUIikKCiMgbWFrZSBzdXJlIElEcyBtYXRjaApzZXhfZ2VuZXNbIXNleF9nZW5lcyAlaW4lIHJvd25hbWVzKGdhbGVuX2FtbF9kb25vcnMpXQoKbGVuZ3RoKHNleF9nZW5lcykKYGBgCgpBbHNvIGV4Y2x1ZGUgZ2VuZXMgZXhwcmVzc2VkIG9uIFkgY2hyb21vc29tZS4KMTQ5IGdlbmVzIHByZXNlbnQgaW4gc2NSTkEtc2VxIGRhdGEuCgpgYGB7cn0KeV9jaHJvbV9nZW5lcyA8LSByZWFkX2xpbmVzKCJyZWZlcmVuY2Vfc291cmNlcy9nZW5jb2RlLnY0NF9nZW5lX25hbWVzX2NoclkudHh0IikKeV9jaHJvbV9nZW5lcyA8LSB5X2Nocm9tX2dlbmVzW3lfY2hyb21fZ2VuZXMgJWluJSByb3duYW1lcyhnYWxlbl9hbWxfZG9ub3JzKV0KbGVuZ3RoKHlfY2hyb21fZ2VuZXMpCgpzZXhfZ2VuZXMgPC0gdW5pcXVlKGMoc2V4X2dlbmVzLCB5X2Nocm9tX2dlbmVzKSkKYGBgCgo2IG91dCBvZiAyMDAwIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyAoQU1MK2hlYWx0aHkpIGhhdmUgc2V4LXNwZWNpZmljIGV4cHJlc3Npb24uCgpJIGRvbid0IHRoaW5rIHJlbW92YWwgb2YgYWxsIGdlbmVzIHRoYXQgYXJlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBiZXR3ZW4gTSBhbmQgRiBzaG91bGQgYmUgcmVtb3ZlLCBiZWNhdXNlIHRoZXJlIHZhcmlhYmxlcyB0aGF0IGNvLXZhcnkgd2l0aCBzZXguIAoKYGBge3J9CiMgc2V4LXNwZWNpZmljIGdlbmVzIHVzZWQgZm9yIGVtYmVkZGluZwpzZXhfZ2VuZXNbc2V4X2dlbmVzICVpbiUgVmFyaWFibGVGZWF0dXJlcyhnYWxlbl9hbWxfZG9ub3JzKV0KYGBgCgpSZS1jYWxjdWxhdGUgUENBLCBleGNsdWRpbmcgc2V4LXNwZWNpZmljIGdlbmVzIGZyb20gSFZHIGxpc3QuCgpgYGB7ciByZXN1bHRzPUZ9CmdhbGVuX2FtbF9kb25vcnMgPC0KICBSdW5QQ0EoCiAgICBnYWxlbl9hbWxfZG9ub3JzLAogICAgZmVhdHVyZXMgPSBWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnMpWyFWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnMpICVpbiUgc2V4X2dlbmVzXSwKICAgIHJlZHVjdGlvbi5uYW1lID0gInBjYV9ub19zZXgiCiAgKQpgYGAKCgpgYGB7cn0KRWxib3dQbG90KGdhbGVuX2FtbF9kb25vcnMsIG5kaW1zID0gNTAsIHJlZHVjdGlvbiA9ICJwY2Ffbm9fc2V4IikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCBOQSkpCmBgYAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzIDwtCiAgUnVuVU1BUCgKICAgIGdhbGVuX2FtbF9kb25vcnMsCiAgICBkaW1zID0gMToxNSwKICAgIHJlZHVjdGlvbiA9ICJwY2Ffbm9fc2V4IiwKICAgIHJlZHVjdGlvbi5uYW1lID0gInVtYXBfbm9fc2V4IgogICkKYGBgCgpJIHdvdWxkIGNoZWNrIGlmIHRoZSBpbnRlZ3JhdGlvbiBvZiBzZXhlcyBoYXMgaW1wcm92ZWQgaWYgQ2FsY0FsaWdubWVudE1ldHJpYyB3YXMgc3RpbGwgYXZhaWxhYmxlIGluIFNldXJhdCB2NS4uLgoKTWl4aW5nTWV0cmljIGhhcyByZXBsYWNlZCBDYWxjQWxpZ25tZW50TWV0cmljIChTZXVyYXQgdjIpLgoKCkV4cGxhbmF0aW9uIG9mIFtNaXhpbmdNZXRyaWNdKGh0dHBzOi8vZ2l0aHViLmNvbS9zYXRpamFsYWIvSW50ZWdyYXRpb24yMDE5L2lzc3Vlcy8xKQoKPiAxLiBMb29rIGF0IHRoZSBrLm1heCBuZWFyZXN0IG5laWdoYm9ycyBmb3IgYSBjZWxsIGFjcm9zcyBhbGwgZGF0YXNldHMuCiAgMi4gQ29tcHV0ZSB0aGUgayBjbG9zZXN0IG5laWdoYm9ycyBmb3IgZWFjaCBkYXRhc2V0IGluZGl2aWR1YWxseSBmb3IgdGhlIHNhbWUgY2VsbC4KICAzLiBGaW5kIHRoZSByYW5rIGluIHRoZSBvdmVyYWxsIG5laWdoYm9yaG9vZCBsaXN0IChmcm9tIDEuKSB0aGF0IGNvcnJlc3BvbmRzIHRvIHRoZSBrdGggbmVpZ2hib3IgaW4gZWFjaCBkYXRhc2V0IChtYXggb2Ygay5tYXgpIGFuZCB0YWtlIHRoZSBtZWRpYW4gYWNyb3NzIGFsbCBkYXRhc2V0cy4KICA0LiBDb21wdXRlIDEtMyBmb3IgZXZlcnkgY2VsbCBhbmQgYXZlcmFnZS4KICBUaGlzIGF2ZXJhZ2UgaXMgdGhlIHZhbHVlIHRoYXQgaXMgcmV0dXJuZWQgYnkgdGhlIE1peGluZ01ldHJpYyBmdW5jdGlvbi4gSG93ZXZlciwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIHRoaW5rIG9mIGhpZ2ggdmFsdWVzIG9mICJtaXhpbmciIHRvIGJlIGJldHRlciBzbyBmb3IgdmlzdWFsaXphdGlvbiwgd2Ugb2Z0ZW4gcGxvdCBtYXguayAtIE1peGluZ01ldHJpYy4KCkRlYWZ1bHQgYG1heC5rID0gMzAwYC4KCkFjY29yZGluZyB0byB0aGlzIG1ldHJpYywgbWl4aW5nIG9mIGNlbGxzIGZyb20gbWFsZSBhbmQgZmVtYWxlIEFNTCBkb25vcnMgaGFzICpzbGlnaHRseSogaW1wcm92ZWQuIAoKYGBge3IgcmVzdWx0cyA9IEZ9Cm1peGluZ19hbWxfc2V4ZXMgPC0gTWl4aW5nTWV0cmljKAogIHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBTYW1wbGVDbGFzcyA9PSAiQW1sRG9ub3IiKSwKICByZWR1Y3Rpb24gPSAicGNhIiwKICBkaW1zID0gMToxNSwKICBncm91cGluZy52YXIgPSAiR2VuZGVyIgopCgptaXhpbmdfYW1sX3NleGVzX2NvcnJlY3RlZCA8LSBNaXhpbmdNZXRyaWMoCiAgc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJBbWxEb25vciIpLAogIHJlZHVjdGlvbiA9ICJwY2Ffbm9fc2V4IiwKICBkaW1zID0gMToxNSwKICBncm91cGluZy52YXIgPSAiR2VuZGVyIgopCmBgYAoKCmBgYHtyfQozMDAgLSBtZWFuKG1peGluZ19hbWxfc2V4ZXMpCjMwMCAtIG1lYW4obWl4aW5nX2FtbF9zZXhlc19jb3JyZWN0ZWQpCmBgYAoKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD03fQojIHNleCBkaWZmZXJlbmNlcwpEaW1QbG90KAogIHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBTYW1wbGVDbGFzcyA9PSAiQW1sRG9ub3IiKSwKICByZWR1Y3Rpb24gPSAidW1hcF9ub19zZXgiLAogIGdyb3VwLmJ5ID0gIkdlbmRlciIsCiAgc2h1ZmZsZSA9IFQsCiAgcHQuc2l6ZSA9IDAuMDA1CikgKyBjb29yZF9lcXVhbCgpCmBgYAoKIyMgVGFzayAyOiBNTCBjbGFzc2lmaWVyIG1hbGlnbmFudCBjZWxscwoKKipUYXNrIERlc2NyaXB0aW9uKioKCkFuIGludGVyZXN0aW5nIGZlYXR1cmUgb2YgdGhlIGRhdGFzZXQgaXMgdGhlIGF2YWlsYWJpbGl0eSBvZiBsYWJlbHMgZGlzdGluZ3Vpc2hpbmcgbWFsaWduYW50IGFuZCBub24tbWFsaWduYW50IHNpbmdsZSBjZWxscy4gClRoZSBzZWNvbmQgdGFzayBpcyB0byBleHBsb2l0IHRoZXNlIGxhYmVscyB0byBidWlsZCBhIHN1aXRhYmxlIE1MIGNsYXNzaWZpZXIgdGhhdCBwcmVkaWN0cyB0aGUgbm9ybWFsIG9yIG1hbGlnbmFudCBpZGVudGl0eSBvZiBzaW5nbGUgY2VsbHMuIApQbGVhc2UgZGV0YWlsIG9uIGhvdyB5b3VyIGNsYXNzaWZpZXIgd29ya3MsIHdoZXRoZXIgaXQgaXMgaW50ZXJwcmV0YWJsZSwgaXRzIHBlcmZvcm1hbmNlLCBhbmQgdGhlIHRyYWluLXRlc3Qgc3BsaXQgeW91IHVzZSBmb3IgeW91ciBtYWNoaW5lIGxlYXJuaW5nIGV4cGVyaW1lbnQuICAKCiMjIyBDb25zaWRlcmF0aW9ucyBmb3IgdGhlIHRhc2sKClRoZSB0cmlja3kgcGFydCBpcyBub3QgdHJhaW5pbmcgYSBjbGFzc2lmaWVyLCBidXQgdG8gY29uc2lkZXIgYmlvbG9neSBhbmQgdGVjaG5vbG9neSB0byB0cmFpbiBhICpnb29kKiBjbGFzc2lmaWVyLiAKCkkgYXNzdW1lIHRoYXQgImxhYmVscyBkaXN0aW5ndWlzaGluZyBtYWxpZ25hbnQgYW5kIG5vbi1tYWxpZ25hbnQgc2luZ2xlIGNlbGxzIiBpbiB0aGUgdGFzayBkZXNjcmlwdGlvbiByZWZlcnMgdG8gdGhlIGdlbm90eXBlZCBjZWxscywgYW5kIG5vdCB0aGUgbGFiZWxzIHByZWRpY3RlZCBieSB2YW4gR2FsZW4gZXQgYWwuIGJhc2VkIG9uIHRoZWlyIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMuICAKClZhbiBHYWxlbiBldCBhbC4gdHJhaW4gdHdvIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMgKHNlZSBiZWxvdyksIGluIG9yZGVyIHRvIGNsYXNzaWZ5IGNlbGxzIGZyb20gQU1MIGRvbm9ycyBpbnRvIG1hbGlnbmFudCBhbmQgbm9ybWFsIGNlbGxzLiAgClRoZSBmaXJzdCBjbGFzc2lmaWVyIGlzIGZvciBIU1BDIGNlbGwgdHlwZXMgYW5kIHRoZSBzZWNvbmQgZm9yIEhTUEMgY2VsbCB0eXBlcyArIG1hbGlnbmFudCB2cy4gbm9ybWFsLiAKRWFjaCBjbGFzc2lmaWVyIGNvbnNpc3RzIG9mIGFuICJvdXRlciIgY2xhc3NpZmllciBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGFuICJpbm5lciIgY2xhc3NpZmllciB0aGF0IGlzIHVzZWQgZm9yIHByZWRpY3Rpb24uIApEdWUgdG8gdmVyeSBsaW1pdGVkIG51bWJlciBvZiBnZW5vdHlwZWQgY2VsbHMgdGhhdCBjYW4gYmUgdXNlZCBmb3IgdHJhaW5pbmcsIHRoZXkgZG8gbm90IGhvbGQgb3V0IGEgdGVzdCBzZXQuIApJbnN0ZWFkLCB0byBldmFsdWF0ZSB0aGVpciBtb2RlbCBwZXJmb3JtYW5jZSwgdGhleSBwZXJmb3JtIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIG9mIHRoZSAiaW5uZXIiIGNsYXNzaWZpZXIuICAKRmluYWxseSwgdGhleSB1c2UgdGhlIG1vZGVsIHRyYWluZWQgb24gYWxsIGF2YWlsYWJsZSBkYXRhIGZvciBjbGFzc2lmaWNhdGlvbiBvZiBBTUwgY2VsbHMgdGhhdCB3ZXJlIG5vdCBnZW5vdHlwZWQuICAKCkkgYXNzdW1lIHRoYXQgYXQgdGhlIHRpbWUgdGhlcmUgd2FzIG5vIGluZGVwZW5kZW50IGRhdGFzZXQgYXZhaWxhYmxlIHdpdGggZ2Vub3R5cGVkIEFNTCBjZWxscyB0byB0ZXN0IGZvciBnZW5lcmFsaXphYmlsaXR5IG9mIHRoZSBtb2RlbCB0byBvdGhlciBzY1JOQS1zZXEgdGVjaG5vbG9naWVzIChkcm9wbGV0LWJhc2VkLCA1JyBzZXF1ZW5jaW5nKSwgaHVtYW4gcG9wdWxhdGlvbnMsIG9yIGRyaXZlciBtdXRhdGlvbnMuIAoKKipQb2ludHMgb2YgY3JpdGlxdWUqKgoKMS4gSW5jbHVkaW5nIGFsbCB0cmFpbmluZyBkYXRhIGluIHRoZSBmZWF0dXJlIHNlbGVjdGlvbiBtYWtlcyB0aGUgQ1YgcmVzdWx0cyBvZiB0aGUgImlubmVyIiBjbGFzc2lmaWVyIGxlc3MgaW5mb3JtYXRpdmUuCjIuIFRoZSBDViBpcyBzdXBwb3NlZCB0byBldmFsdWF0ZSBvdmVyLSBhbmQgdW5kZXJmaXR0aW5nLgogICAgVG8gZXN0aW1hdGUgb3ZlcmZpdHRpbmcgKGNvbXBsZXhpdHkgb2YgdGhlIG1vZGVsKSwgdGhlIG1vZGVsIHNob3VsZCBiZSBldmFsdWF0ZWQgb24gaG93IHdlbGwgaXQgZXh0cmFwb2xhdGVzIHRvIGFub3RoZXIgZG9ub3IgYW5kIHRvIGFub3RoZXIgZHJpdmVyIG11dGF0aW9uLCBub3QgYSByYW5kb20gc2FtcGxlIG9mIGFsbCB0cmFpbmluZyBjZWxscy4KMy4gQWxsIGhlYWx0aHkgY2VsbHMgaW4gdGhlIHRyYWluaW5nIGRhdGEgYXJlIG1hbGUuIFZhbiBHYWxlbiBkbyBub3QgYWRkcmVzcyB0aGlzIGJ5IHJlbW92aW5nIHNleC1zcGVjaWZpYyBnZW5lcyBmcm9tIHRoZSBmZWF0dXJlcy4gCiAgICAoSSdtIGhvbmVzdGx5IHN1cHJpc2VkIHRoYXQgdGhlIGNsYXNzaWZpZXIgZG9lc24ndCBwZXJmb3JtIGV2ZW4gd29yc2Ugb24gZmVtYWxlIHNhbXBsZXMgYmFzZWQgb24gYmxhc3QgY291bnQgdGhhbiBpdCBkb2VzLiAKICAgIEEgbWFsZSBzYW1wbGUgd2l0aCBsb3NzIG9mIHkgbWlnaHQgcGxheSBhIHJvbGUsIGFuZCBleHByZXNzaW9uIGxldmVsL3NwYXJzaXR5IG9mIHNleC1zcGVjaWZpYyBnZW5lcy4pCjQuIENlbGxzIHdlcmUgZ2Vub3R5cGVkIGJhc2VkIG9uIHRyYW5zY3JpcHRzIG9mIGZvbGxvd2luZyBnZW5lczogIAogICAgVEVUMiwgU0VURDIsIFBUUE4xMSwgTlJBUywgU0YzQTEsIEJDT1IsIEtJVCwgUkFEMjEsIFRQNTMsIEJSQ0MzLCBSVU5YMSwgRkxUMywgSURIMiwgRE5NVDNBLCBTTUMzLCBOUE0xLCBLUkFTLiAgCiAgICBDZWxscyB3aXRoIGhpZ2hlciBleHByZXNzaW9uIG9mIHRoZXNlIGdlbmVzIGFyZSBtb3JlIGxpa2VseSB0byBiZSBnZW5vdHlwZWQgYW5kIG1ha2UgaXQgaW50byB0aGUgdHJhaW5pbmcgZGF0YS4KICAgIFRoZWlyIGV4cHJlc3Npb24gc2hvdWxkIGJlIGV4Y2x1ZGVkIGZyb20gdGhlIGZlYXR1cmUgbGlzdCwgYWxzbyB0byBtYWtlIGl0IG1vcmUgZ2VuZXJhbGlzYWJsZSB0byBvdGhlciBkcml2ZXIgbXV0YXRpb25zLiAKNS4gVGhlIGNsYXNzaWZpZXIgZG9lcyBub3QgY29uc2lkZXIgcG9zc2libGUgZGlmZmVyZW5jZXMgaW4gdHJhbnNjcmlwdGlvbiBiZXR3ZWVuIG5vbi1tYWxpZ25hbnQgSFNQQ3MgZnJvbSBBTUwgZG9ub3JzIGFuZCBoZWFsdGh5IGRvbm9ycy4gCiAgICBOb24tbWFsaWduYW50IGNlbGxzIGZyb20gQU1MIGRvbm9ycyBoYXZlIHBvc3NpYmx5IGFsdGVyZWQgdHJhbnNjcmlwdGlvbiBkdWUgdG8gY2hhbmdlcyBpbiB0aGUgZW52aXJvbm1lbnQgaW4gdGhlIGJvbmUgbWFycm93LiAgCiAgICBUaGUgY2xhc3NpZmllciBvbmx5IGNvbnNpZGVycyBoZWFsdGh5IGNlbGxzIGZyb20gaGVhbHRoeSBkb25vcnMsIGFuZCBub3Qgbm9uLW1hbGlnbmFudCBjZWxscyBmcm9tIEFNTCBkb25vcnMuIAogICAgVGhpcyBpcyBhIGxpbWl0YXRpb24gb2YgdGhlIHRlY2hub2xvZ3ksIGJlY2F1c2UgY2VsbHMgd2l0aCB3dCB0cmFuc2NyaXB0IGRldGVjdGVkIGJ1dCBubyBtdXRhdGVkIHRyYW5zY3JpcHQgZGV0ZWN0ZWQgY2Fubm90IGNvbmZpZGVudGx5IGJlIGNsYXNzaWZpZWQgYXMgbm9ybWFsLCBkdWUgdG8gY2xvbmFsaXR5IGFuZCBoZXRlcm96eWdvc2l0eS4gIAogICAgSG93ZXZlciwgdGhlcmUgbWlnaHQgYmUgYSBzdWJzZXQgb2YgY2VsbHMgZnJvbSBkb25vcnMgd2l0aCBrbm93biB6eWdvc2l0eSBhbmQgY2xvbmFsIHN0cnVjdHVyZSB3aGVyZSB0aGlzIGlzIHBvc3NpYmxlLiAKCldoYXQgSSBhZ3JlZSB3aXRoLCBpcyB0aGUgb2JzZXJ2YXRpb24gdGhhdCwgZ2VuZXJhbGx5LCBtYWxpZ25hbnQgSFNDcyBhcmUgdHJhbnNjcmlwdGlvbmFsbHkgbW9yZSBzaW1pbGFyIHRvIGhlYWx0aHkgSFNDcyB0aGFuIHRvIG1hbGlnbmFudCBtb25vY3l0ZSBwcm9nZW5pdG9ycy4KVGhlcmVmb3JlLCBpdCBtYWtlcyBzZW5zZSB0byBndWlkZSB0aGUgbW9kZWwgd2l0aCBpbmZvcm1hdGlvbiBvbiBzaW1pbGFyaXR5IHRvd2FyZHMgaGVhbHRoeSBIU1BDIGNlbGwgdHlwZXMuICAKClJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMgYWxsb3cgdGhlIGV4dHJhY3Rpb24gb2YgZmVhdHVyZSBpbXBvcnRhbmNlIG1ldHJpY3MgKGUuZy4gbnVtYmVyIG9mIHRyZWVzIGluIHdoaWNoIGEgZmVhdHVyZSBpcyB1c2VkKS4KSG93ZXZlciwgb25lIGhhcyB0byBkaWZmZXIgYmV0d2VlbiBhICptaW5pbWFsIG9wdGltYWwgc3Vic2V0KiBhbmQgYW4gKmFsbCByZWxldmFudCBzdWJzZXQqIG9mIGdlbmVzLiAKVGhpcyBpcyBlc3BlY2lhbGx5IHJlbGV2YW50IGZvciBnZW5lLWV4cHJlc3Npb24gYmFzZWQgY2xhc3NpZmllcnMgZHVlIHRvIHJlZHVuZGFuY2UgKHRoaW5rIGdlbmUgbW9kdWxlcy9zaWduYXR1cmVzKS4KU2VlIFtLdXJzYSBldCBhbC4gMjAxNF0oaHR0cHM6Ly9ibWNiaW9pbmZvcm1hdGljcy5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2LzE0NzEtMjEwNS0xNS04KSBmb3IgbW9yZSBkZXRhaWxzLiAKClZhbiBHYWxlbiBldCBhbC4gc2VsZWN0IGFzIGZlYXR1cmVzIHRoZSBnZW5lcyBjaG9zZW4gbW9zdCBvZnRlbiBpbiB0aGUgb3V0ZXIgY2xhc3NpZmllciwgYWNyb3NzIDEwMDAgdHJlZXMuIFRoaXMgZG9lcyBub3Qgc2VlbSB0byBjb25zaWRlciBjbGFzc2VzLiAKCioqSWRlYXMqKgoKLSBJbnN0ZWFkIG9mIHByZWRpY3RpbmcgSFNDIGFuZCBIU0MtbGlrZSBjZWxscywgd2h5IG5vdCBnaXZlIGEgc2ltaWxhcml0eSBzY29yZSB0b3dhcmRzIGVhY2ggaGVhbHRoeSBjZWxsIHR5cGUgYXMgaW5wdXQgZmVhdHVyZS4gCiAgICBUaGlzIHdvdWxkIGFsbG93IHRvIGluZm9ybSB0aGUgY2xhc3NpZmllciBmb3IgcmVsZXZhbnQgZmVhdHVyZXMgbGlrZSBjb21iaW5hdGlvbnMgb2Ygc2lnbmF0dXJlcyAoZS5nLiBMU0NzIGFyaXNpbmcgZnJvbSBHTVBzIHdpdGggbWl4ZWQgSFNDL0dNUCBzaWduYXR1cmUpLgotIFdoeSBpbmNsdWRlIGx5bXBob2lkIGFuZCBlcnl0aHJvaWQgY2VsbCB0eXBlcyBpbiB0aGUgbWFsaWduYW50IHZzLiBub24tbWFsaWduYW50IGNsYXNzaWZpZXI/IAogICAgVGhleSBhcmUgdHJhbnNjcmlwdGlvbmFsbHkgdmVyeSBkaXN0aW5jdCBhbmQgaW4gQU1MIHRoZXkgYXJlIG5vdCBtYWxpZ25hbnQuIAogICAgVGhlIGNsYXNzaWZpZXIgbWlnaHQgaGF2ZSBhbiBlYXNpZXIgdGltZSBpZiB0aGV5IGFyZSBleGNsdWRlZC4gCi0gQ29uc2lkZXIgb3RoZXIgbWV0aG9kcyB0byBhc3NpZ24gdGhlIG1vc3Qgc2ltaWxhciBoZWFsdGh5IGNlbGwgdHlwZSwgc3VjaCBhcyBsYWJlbCB0cmFuc2ZlciB2aWEgcHJvamVjdGlvbiBvbnRvIHRoZSBoZWFsdGh5IHJlZmVyZW5jZSAoQXppbXV0aCkuIAotIFVzZSBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldCBvZiBnZW5vdHlwZXMgQU1MIGNlbGxzLiAKCioqVmFuIEdhbGVuIGNsYXNzaWZpZXIgbWFsaWduYW50IHZzLiBub3JtYWwgQU1MIGNlbGxzKioKCmBgYHtyIGVjaG8gPSBGLCBvdXQud2lkdGg9IjEwMCUiLCBmaWcud2lkdGg9MTV9Cm5vbW5vbWw6Om5vbW5vbWwoY29kZSA9ICIjZGlyZWN0aW9uOiBkb3duCls8ZnJhbWU+SFNQQyBjZWxsdHlwZSBhbm5vdGF0aW9uIHwKW0hlYWx0aHkgY2VsbHNdIC0gCls8c3RhdGU+IEJhY2tzcGluIGNsdXN0ZXJpbmcgfCBNYW51YWwgSFNQQyBjZWxsIHR5cGUgYW5ub3RhdGlvbl0gLSAKW0hlYWx0aHkgY2VsbHMsIGFubm90YXRlZCBieSBIU1BDIGNlbGwgdHlwZXNdIC0gWzxzdGF0ZT4gVHJhaW4gUkZdIC0gCltDbGFzc2lmaWVyIGZvciBIU1BDIGNlbGwgdHlwZXNdIC0gWzxzdGF0ZT4gUHJlZGljdF0KW0FNTCBjZWxsc10gLSBbPHN0YXRlPiBQcmVkaWN0XSAtCltBTUwgY2VsbHMsIGFubm90YXRlZCBieSBIU1BDIGNlbGwgdHlwZXNdCl0KCls8ZnJhbWU+TWFsaWduYW50IGNlbGx0eXBlIGFubm90YXRpb24gfApbQU1MIGNlbGxzLCBnZW5vdHlwZWQgbWFsaWduYW50IHwgSFNQQy1saWtlIGNlbGwgdHlwZSBsYWJlbHNdIC0gWzxzdGF0ZT4gVHJhaW4gUkZdCltIZWFsdGh5IGNlbGxzIHwgSFNQQyBjZWxsIHR5cGUgbGFiZWxzXSAtIFs8c3RhdGU+IFRyYWluIFJGXSAtCltDbGFzc2lmaWVyIGZvciBIU1BDICYgSFNQQy1saWtlIGNlbGwgdHlwZXNdIC0gWzxzdGF0ZT4gUHJlZGljdF0KW0FNTCBjZWxscywgbm90IGdlbm90eXBlZCBtYWxpZ25hbnQgfCBIU1BDLWxpa2UgY2VsbCB0eXBlIGxhYmVsc10gLSBbPHN0YXRlPiBQcmVkaWN0XSAtCltBTUwgY2VsbHMsIGFubm90YXRlZCBieSBIU1BDICYgSFNQQy1saWtlIGNlbGwgdHlwZXNdCl0iKQpgYGAKCgojIyMgVHJhbnNjcmlwdGlvbmFsIGhldGVyb2dlbmVpdHkgb2YgbWFsaWduYW50IGFuZCBoZWFsdGh5IGNlbGxzCgpUaGUgZ29hbCBpcyB0byBkZXZlbG9wIGEgY2xhc3NpZmllciBvZiBtYWxpZ25hbnQgdnMuIGhlYWx0aHkgSFNQQ3MgYW5kIG15ZWxvaWQgY2VsbHMgZnJvbSBBTUwgcGF0aWVudHMsIHRoYXQgaXMgZ2VuZXJhbGl6YWJsZSB0byBvdGhlciBBTUwgdHlwZXMgd2l0aCBvdGhlciBkcml2ZXIgbXV0YXRpb25zIGFuZCBvdGhlciBzY1JOQS1zZXEgdGVjaG5vbG9naWVzL2RhdGFzZXRzLiAKCkV2YWx1YXRlIHRoZSB0cmFuc2NyaXB0aW9uYWwgaGV0ZXJvZ2VuZWl0eSBvZiBBTUwgc3VidHlwZXMgYW5kIG1hbGlnbmFudCB2cy4gaGVhbHRoeSBjZWxscy4KClExOiBEbyBoZWFsdGh5IGNlbGxzIGRpZmZlciBmcm9tIG1hbGlnbmFudCBjZWxscyBiYXNlZCBvbiB0aGVpciB0cmFuc2NyaXB0b21lPwoKUTI6IElzIHRoZSBtYWxpZ25hbnQgdHJhbnNjcmlwdGlvbmFsIHByb2ZpbGUgc3BlY2lmaWMgdG8gZHJpdmVyIG11dGF0aW9ucz8KCldlIGFyZSBvbmx5IGludGVyZXN0ZWQgaW4gbXllbG9pZCBhbmQgSFNQQyBjZWxscyBoZXJlLCBiZWNhdXNlIGRpc3RpbmN0aW9uIG9mIGVyeXRocm9pZCBhbmQgbHltcGhvaWQgY2VsbHMgaXMgcmVsYXRpdmVseSBlYXN5LiAKVGhvc2UgY2VsbHMgYXJlIG5vdCBtYWxpZ25hbnQgaW4gYWN1dGUgKm15ZWxvaWQqIGxldWtlbWlhLiAKCkZvciBhIHN0YXJ0LCB1c2UgSFNQQ3MgKyBteWVsb2lkIGNlbGxzLCBhY2NvcmRpbmcgdG8gdmFuIEdhbGVuIGNlbGwgdHlwZSBhbm5vdGF0aW9uLiAgCgo8IS0tIE9ubHkgY29uc2lkZXIgc2FtcGxlcyB3aXRoIGJsYXN0IGNvdW50ID4gMC4xIGFuZCBhdCB0aW1lIG9mIGRpYWdub3Npcy4gIC0tPgo8IS0tIFRoZXNlIHdlcmUgMiB2YXJpYWJsZXMgdGhhdCBhY2NvdW50ZWQgZm9yIG1vc3Qgb2YgdGhlIHZhcmlhbmNlIGluIHRoZSBkYXRhLiAgLS0+CgpJbmNsdWRlIGhlYWx0aHkgZG9ub3JzLCB0byBzZWUgaWYgdGhlIGNlbGxzIGdlbm90eXBlZCBhcyB3dCBncm91cCB3aXRoIGRlZmluaXRpdmUgaGVhbHRoeSBjZWxscy4gCgpgYGB7cn0Kc2VsZWN0ZWRfc2FtcGxlcyA8LSBkb25vcl9tZXRhZGF0YSAlPiUKICAjIGZpbHRlcigKICAjICAgIWdyZXBsKCJCTSIsIFNhbXBsZSksCiAgIyAgICMgYEJsYXN0IGNvdW50YCA+IDAuMSwKICAjICAgIyBgRGF5cyBmcm9tIGRpYWdub3Npc2AgPT0gIkQwIgogICMgICApICU+JQogIHB1bGwoU2FtcGxlSWQpCgpnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwYyA8LQogIHN1YnNldCgKICAgIGdhbGVuX2FtbF9kb25vcnMsCiAgICBzdWJzZXQgPSAob3JpZy5pZGVudCAlaW4lIHNlbGVjdGVkX3NhbXBsZXMgJiBTYW1wbGVDbGFzcyA9PSAiSGVhbHRoeURvbm9yIiB8IAogICAgICAoKENlbGxUeXBlR2VuZXJhbCA9PSAiTXllbG9pZC9IU1BDLWxpa2UiIHwKICAgICAgQ2VsbFR5cGVHZW5lcmFsID09ICJNeWVsb2lkL0hTUEMiKSAmIGdlbm90eXBlICE9ICJub3RfZGV0ZWN0ZWQiKSkKICApCnRhYmxlKGdhbGVuX2FtbF9kb25vcnNfbXllbG9pZF9oc3BjJGdlbm90eXBlKQoKIyBvbmx5IGtlZXAgU2FtcGxlSWQtZ2Vub3R5cGUgY29tYmluYXRpb25zIHdpdGggYXQgbGVhc3QgMTAgY2VsbHMKc2FtcGxlc19lbm91Z2hfY2VsbHMgPC0gbmFtZXMod2hpY2godGFibGUoZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGMkb3JpZy5pZGVudCkgPj0gMTApKQpnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwYyA8LQogIHN1YnNldCgKICAgIGdhbGVuX2FtbF9kb25vcnNfbXllbG9pZF9oc3BjLAogICAgc3Vic2V0ID0gb3JpZy5pZGVudCAlaW4lIHNhbXBsZXNfZW5vdWdoX2NlbGxzCiAgKQpgYGAKCmBgYHtyfQpnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwY0BtZXRhLmRhdGEgPC0gZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGNAbWV0YS5kYXRhICU+JQogIG11dGF0ZSgKICAgIFNhbXBsZUlkX2dlbm90eXBlID0KICAgICAgcGFzdGUoCiAgICAgICAgZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGMkb3JpZy5pZGVudCwKICAgICAgICBnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwYyRnZW5vdHlwZSwKICAgICAgICBzZXAgPSAiXyIKICAgICAgKQogICkgJT4lCiAgbXV0YXRlKFNhbXBsZUlkX2dlbm90eXBlID0gaWZlbHNlKFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiLCBnc3ViKCJub3RfZGV0ZWN0ZWQiLCAiaGVhbHRoeSIsIFNhbXBsZUlkX2dlbm90eXBlKSwgU2FtcGxlSWRfZ2Vub3R5cGUpKQpgYGAKCgpgYGB7cn0KcGJfY291bnRzIDwtCiAgU2V1cmF0OjpBZ2dyZWdhdGVFeHByZXNzaW9uKAogICAgZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGMsCiAgICBhc3NheXMgPSAiUk5BIiwKICAgIHNsb3QgPSAiY291bnRzIiwKICAgICMgZ3JvdXAuYnkgPSAiU2FtcGxlSWRfQ2VsbFR5cGVHZW5lcmFsIgogICAgZ3JvdXAuYnkgPSAiU2FtcGxlSWRfZ2Vub3R5cGUiCiAgICAjIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiCiAgKSRSTkEKCmdyb3VwX2Fubm8gPC0gY29sbmFtZXMocGJfY291bnRzKQpncm91cF9hbm5vIDwtIGdzdWIoIi4qLSIsICIiLCBncm91cF9hbm5vKQogCnBiX2RnZSA8LSBlZGdlUjo6REdFTGlzdCgKICAgIGNvdW50cyA9IHBiX2NvdW50cywKICAgIHNhbXBsZXMgPSBjb2xuYW1lcyhwYl9jb3VudHMpLAogICAgZ3JvdXAgPSBncm91cF9hbm5vCikKYGBgCgpGaWx0ZXIgb3V0IHNhbXBsZXMgd2l0aCBsb3cgcmVhZCBjb3VudCAoPWZldyBmaWx0ZXJlZCBjZWxscykuICAKS2VlcCBzYW1wbGVzIHdpdGggcmVhZCBjb3VudHMgPiA1MCwwMDAsIHdoaWNoIGlzIGFsbCBzYW1wbGVzLgoKYGBge3J9CnN1bW1hcnkocGJfZGdlJHNhbXBsZXMkbGliLnNpemUpCgprZWVwLnNhbXBsZXMgPC0gcGJfZGdlJHNhbXBsZXMkbGliLnNpemUgPiA1ZTQKdGFibGUoa2VlcC5zYW1wbGVzKQoKIyBwYl9kZ2UgPC0gcGJfZGdlWywga2VlcC5zYW1wbGVzXQpgYGAKCkZpbHRlciBvdXQgbG93bHkgZXhwcmVzc2VkIGdlbmVzLiAgCktlZXAgZ2VuZXMgd2l0aCBhdCBsZWFzdCAxMCBDUE0gaW4gYXQgbGVhc3QgMiBzYW1wbGVzLgogCmBgYHtyfQojIGdlbmVzIHdpdGggYXQgbGVhc3QgMSBDUE0gaW4gYXQgbGVhc3QgMiBzYW1wbGVzCmtlZXAuZ2VuZXMgPC0gcm93U3VtcyhlZGdlUjo6Y3BtKHBiX2RnZSkgPj0xMCkgPj0gMgp0YWJsZShrZWVwLmdlbmVzKQogCnBiX2RnZSA8LSBwYl9kZ2Vba2VlcC5nZW5lcywgLCBrZWVwPUZBTFNFXQpgYGAKClRNTSBub3JtYWxpemF0aW9uIGlzIHBlcmZvcm1lZCB0byBlc3RpbWF0ZSBlZmZlY3RpdmUgbGlicmFyeSBzaXplLgogCmBgYHtyfQpwYl9kZ2UgPC0gZWRnZVI6OmNhbGNOb3JtRmFjdG9ycyhwYl9kZ2UsIG1ldGhvZCA9ICJUTU0iKQpgYGAKIApDYWxjdWxhdGUgTXVsdGlkaW1lbnNpb25hbCBzY2FsaW5nIChNRFMpIHBsb3Qgb2YgZGlzdGFuY2VzIGJldHdlZW4gZ2VuZSBleHByZXNzaW9uIHByb2ZpbGVzLgogCmBgYHtyfQpwYl9tZHMgPC0gbGltbWE6OnBsb3RNRFMocGJfZGdlLCBwbG90ID0gRikKCnBiX21kc19kZiA8LSBkYXRhLmZyYW1lKAogIGxpc3QoCiAgICB4ID0gcGJfbWRzJHgsCiAgICB5ID0gcGJfbWRzJHksCiAgICBsaWIuc2l6ZSA9IHBiX2RnZSRzYW1wbGVzJGxpYi5zaXplLAogICAgc2FtcGxlID0gZ3N1YigiLS4qIiwgIiIsIGNvbG5hbWVzKHBiX2RnZSkpLAogICAgZ3JvdXAgPSBwYl9kZ2Ukc2FtcGxlcyRncm91cAogICkKKSAlPiUKICBtZXJnZShkb25vcl9tZXRhZGF0YSwgYnkueCA9ICJzYW1wbGUiLCBieS55ID0gIlNhbXBsZUlkIiwgYWxsLnggPSBUKQpgYGAKCkNoZWNrIHRoYXQgbWFqb3Igc291cmNlIG9mIHZhcmlhdGlvbiBpcyBub3QgdGVjaG5pY2FsIChzYW1wbGUgc2l6ZSkgb3IgaXJyZWxldmFudCBiaW9sb2dpY2FsIGluZm9ybWF0aW9uIGluIHRoaXMgY2FzZSAoYmxhc3QgY291bnQpLgoKYGBge3IgZmlnLmhlaWdodD0zLjUsIGZpZy53aWR0aD01fQp4bGFiX3RleHQgPC0gcGFzdGUwKAogICAgIkxlYWRpbmcgbG9nRkMgZGltIDEgKCIsCiAgICByb3VuZChwYl9tZHMkdmFyLmV4cGxhaW5lZFsxXSAqIDEwMCwgMSksCiAgICAiJSkiCiAgKQp5bGFiX3RleHQgPC0gcGFzdGUwKAogICAgIkxlYWRpbmcgbG9nRkMgZGltIDIgKCIsCiAgICByb3VuZChwYl9tZHMkdmFyLmV4cGxhaW5lZFsyXSAqIDEwMCwgMSksCiAgICAiJSkiCiAgKQoKbWRzX3RoZW1lIDwtIHRoZW1lX2J3KCkgKwogIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCgpwYl9tZHNfZGYgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4LCB5LCBsYWJlbCA9IHNhbXBsZSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGxvZyhsaWIuc2l6ZSkpKSArCiAgIyBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoKSArCiAgeGxhYih4bGFiX3RleHQpICsgeWxhYih5bGFiX3RleHQpICsKICBtZHNfdGhlbWUKCnBiX21kc19kZiAlPiUKICBnZ3Bsb3QoYWVzKHgsIHksIGxhYmVsID0gc2FtcGxlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYEJsYXN0IGNvdW50YCkpICsKICAjIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgpICsKICB4bGFiKHhsYWJfdGV4dCkgKyB5bGFiKHlsYWJfdGV4dCkgKwogIG1kc190aGVtZQoKcGJfbWRzX2RmICU+JQogIGdncGxvdChhZXMoeCwgeSwgbGFiZWwgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBgRGF5cyBmcm9tIGRpYWdub3Npc2AgPT0gIkQwIikpICsKICAjIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgpICsKICB4bGFiKHhsYWJfdGV4dCkgKyB5bGFiKHlsYWJfdGV4dCkgKwogIG1kc190aGVtZQoKcGJfbWRzX2RmICU+JQogIGdncGxvdChhZXMoeCwgeSwgbGFiZWwgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBncm91cCkpICsKICAjIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgpICsKICB4bGFiKHhsYWJfdGV4dCkgKyB5bGFiKHlsYWJfdGV4dCkgKwogIG1kc190aGVtZQoKcGJfbWRzX2RmICU+JQogIGdncGxvdChhZXMoeCwgeSwgbGFiZWwgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBHZW5kZXIpKSArCiAgIyBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoKSArCiAgeGxhYih4bGFiX3RleHQpICsgeWxhYih5bGFiX3RleHQpICsKICBtZHNfdGhlbWUKCnBiX21kc19kZiAlPiUKICBnZ3Bsb3QoYWVzKHgsIHksIGxhYmVsID0gc2FtcGxlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gU2FtcGxlKSkgKwogIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgKICAgIHNpemUgPSAyLAogICAgbWluLnNlZ21lbnQubGVuZ3RoID0gMCwKICAgIG1heC5vdmVybGFwcyA9IEluZgogICkgKwogIHhsYWIoeGxhYl90ZXh0KSArIHlsYWIoeWxhYl90ZXh0KSArCiAgbWRzX3RoZW1lICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIpCmBgYAoKCkNoZWNrIGlmIHRoZSBzYW1wbGVzIHNwbGl0IGJ5IG11dGF0aW9uLgoKYGBge3J9CnNhbXBsZUlkX211dGF0aW9uX2xpc3QgPC0gZG9ub3JfbWV0YWRhdGEgJT4lCiAgc2VsZWN0KFNhbXBsZUlkLCBgUkhQIE11dGF0aW9uc2AsIGBDb21tb24gdHJhbnNsb2NhdGlvbmApICU+JQogIGZpbHRlcihTYW1wbGVJZCAlaW4lIHBiX21kc19kZiRzYW1wbGUpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhgUkhQIE11dGF0aW9uc2AsIGBDb21tb24gdHJhbnNsb2NhdGlvbmApLCB2YWx1ZXNfdG8gPSAiZHJpdmVyX211dGF0aW9uIikgJT4lCiAgc2VsZWN0KC1uYW1lKSAlPiUKICBmaWx0ZXIoIWRyaXZlcl9tdXRhdGlvbiAlaW4lIGMoIk5vdCBwZXJmb3JtZWQiLCAiTm9uZSBEZXRlY3RlZCIsICJVbmtub3duIiwgIk5BIikgJiAhaXMubmEoZHJpdmVyX211dGF0aW9uKSkgJT4lCiAgc2VwYXJhdGVfbG9uZ2VyX2RlbGltKGNvbHMgPSBkcml2ZXJfbXV0YXRpb24sICIvLy8gIikgJT4lCiAgbXV0YXRlKGRyaXZlcl9tdXRhdGlvbiA9IGdzdWIoIiAuKiIsICIiLCBkcml2ZXJfbXV0YXRpb24pKSAlPiUKICAjIHVuaXF1ZSBmb3Igc2FtcGxlcyB3aXRoIG11bHRpcGxlIG11dGF0aW9ucyBpbiBzYW1lIGdlbmUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUodmFsdWUgPSBUKSAlPiUKICBwaXZvdF93aWRlcihpZF9jb2xzID0gU2FtcGxlSWQsIG5hbWVzX2Zyb20gPSBkcml2ZXJfbXV0YXRpb24sIHZhbHVlc19maWxsID0gRikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtU2FtcGxlSWQsIG5hbWVzX3RvID0gImRyaXZlcl9tdXRhdGlvbiIpCmBgYAoKCmBgYHtyfQpwYl9tZHNfZGZfZHJpdmVyX211dGF0aW9uIDwtIG1lcmdlKHBiX21kc19kZiwgc2FtcGxlSWRfbXV0YXRpb25fbGlzdCwgYnkueCA9ICJzYW1wbGUiLCBieS55ID0gIlNhbXBsZUlkIiwgYWxsLnggPSBUKQpgYGAKClBDMSBhbGlnbnMgd2l0aCBETk1UQTMuIEluZGljYXRpb24gdGhhdCBETk1UMyBoYXMgYSBkaXN0aW5jdCB0cmFuc2NyaXB0aW9uYWwgcHJvZmlsZS4gCgoiRE5NVDNBIG11dGF0aW9ucyBvY2N1ciBpbiBhcHByb3hpbWF0ZWx5IDIwJSBvZiBBTUwgY2FzZXMgYW5kIGFyZSBhc3NvY2lhdGVkIHdpdGggY2hhbmdlcyBpbiBETkEgbWV0aHlsYXRpb24uIENES04yQiBwbGF5cyBhbiBpbXBvcnRhbnQgcm9sZSBpbiB0aGUgcmVndWxhdGlvbiBvZiBoZW1hdG9wb2lldGljIHByb2dlbml0b3IgY2VsbHMgYW5kIEROTVQzQSBtdXRhdGlvbiBpcyBhc3NvY2lhdGVkIHdpdGggQ0RLTjJCIHByb21vdGVyIG1ldGh5bGF0aW9uLiIKaHR0cHM6Ly9wbWMubmNiaS5ubG0ubmloLmdvdi9hcnRpY2xlcy9QTUM3MTA2MTIyLwoKS1JBUywgRkxUMy1JVEQsIE5QTTEsIE5SQVMKCmBgYHtyLCBmaWcuaGVpZ2h0PTh9CnBiX21kc19kZl9kcml2ZXJfbXV0YXRpb24gJT4lCiAgZmlsdGVyKGdyb3VwID09ICJtYWxpZ25hbnQiKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHksIGxhYmVsID0gc2FtcGxlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdmFsdWUpKSArCiAgZmFjZXRfd3JhcCh+ZHJpdmVyX211dGF0aW9uKSArCiAgeGxhYih4bGFiX3RleHQpICsgeWxhYih5bGFiX3RleHQpICsKICB0aGVtZV9idygpICsKICB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArCiAgY29vcmRfZXF1YWwoKQpgYGAKCmBgYHtyfQp1bmlxdWUoc2FtcGxlSWRfbXV0YXRpb25fbGlzdCRkcml2ZXJfbXV0YXRpb24pCmdlbmVzX2dlbm90eXBlcyA8LQogIGMoCiAgICAiTlJBUyIsCiAgICAiS1JBUyIsCiAgICAiU0YzQTEiLAogICAgIk5PVENIMSIsCiAgICAiTk9UQ0gyIiwKICAgICJTRjNBMSIsCiAgICAiQ0JGQiIsCiAgICAiTVlIMTEiLAogICAgIk5QTTEiLAogICAgIkpBSzMiLAogICAgIkROTVQzQSIsCiAgICAiRkxUMyIsCiAgICAiVFA1MyIsCiAgICAiU0VURDIiLAogICAgIlJVTlgxIiwKICAgICJCQ09SIiwKICAgICJCQ09STDEiLAogICAgIlBUUE4xMSIsCiAgICAiU01DMyIsCiAgICAiSURIMiIsCiAgICAiVEVUMiIsCiAgICAiQlJDQzMiLAogICAgIktJVCIsCiAgICAiUkFEMjEiLAogICAgIk1MTCIsCiAgICAiQ0VCUEEiCiAgKQpgYGAKCiMjIyBBemltdXRoIHByb2plY3Rpb24KCkFsdGVybmF0aXZlIHRvIGNlbGwgdHlwZSBpZGVudGlmaWNhdGlvbiB3aXRoIGEgcmFuZG9tIGZvcmVzdCBtb2RlbCBpcyBsYWJlbCB0cmFuc2ZlciBmcm9tIGEgcmVmZXJlbmNlIGF0bGFzLiAKClBlcmZvcm0gbGFiZWwgdHJhbnNmZXIgZnJvbSB0aGUgQXppbXV0aCBib25lIG1hcnJvdyByZWZlcmVuY2UgYXRsYXMuIApJbnN0ZWFkIG9mIHRyYWluaW5nIGEgcmFuZG9tIGZvcmVzdCBjbGFzc2lmaWVyIHRvIGRldGVybWluZSB0aGUgbW9zdCBzaW1pbGFyIGhlYWx0aHkgZXF1aXZhbGVudCBvZiBhIGNlbGwuICAKCkF6aW11dGggcHJvdmlkZXMgY2VsbCB0eXBlIGxhYmVscyBhdCAyIGxldmVscywgc2VlIFtpbnRlcmFjdGl2ZSByZWZlcmVuY2VdKGh0dHBzOi8vYXppbXV0aC5odWJtYXBjb25zb3J0aXVtLm9yZy9yZWZlcmVuY2VzL2h1bWFuX2JvbmVtYXJyb3cvKS4KCkZvciB0aGlzIHN0ZXAsIHdlIGFyZSBub3QgaW50ZXJlc3RlZCBpbiB0aGUgZ2VuZXMgZGlzdGluZ3Vpc2hpbmcgdGhlIGRpZmZlcmVudCBjZWxsIHR5cGVzIGJlY2F1c2UgdGhleSBhcmUgYWxyZWFkeSB3ZWxsIGRlZmluZWQuIApTbyB3ZSBkb24ndCBuZWVkIGEgUkYgY2xhc3NpZmllciB3aGVyZSB3ZSBjYW4gZXh0cmFjdCBmZWF0dXJlIGltcG9ydGFuY2UgbWV0cmljcy4gCgpUaGUgbGFiZWwgdHJhbnNmZXIgaXMgdmVyeSBmYXN0IGFuZCBlYXN5IGFuZCBwcm92aWRlcyBhIHdlbGwgZXN0YWJsaXNoZWQgcmVmZXJlbmNlIGZyYW1ld29yayBvZiBjZWxsIHR5cGUgY2xhc3NpZmljYXRpb25zIGFuZCBoaWVyYXJjaGllcy4gCgpSZWZlcmVuY2UgZG93bmxvYWRlZCBmcm9tIFtoZXJlXShodHRwOi8vc2V1cmF0Lm55Z2Vub21lLm9yZy9zcmMvY29udHJpYi9ib25lbWFycm93cmVmLlNldXJhdERhdGFfMS4wLjAudGFyLmd6KS4KCmBgYHtyfQpsaWJyYXJ5KEF6aW11dGgpCmxpYnJhcnkoU2V1cmF0RGF0YSkKYGBgCgpgYGB7ciByZXN1bHRzPUYsIGV2YWw9Rn0KIyBUaGUgUnVuQXppbXV0aCBmdW5jdGlvbiBjYW4gdGFrZSBhIFNldXJhdCBvYmplY3QgYXMgaW5wdXQKIyBvdmVyd3JpdGVzIG1pc2NlbGxhbm91cyBkYXRhLCBzbyBzYXZpbmcgYXMgbmV3IG9iamVjdApnYWxlbl9hbWxfZG9ub3JzX2F6aW11dGggPC0gUnVuQXppbXV0aChnYWxlbl9hbWxfZG9ub3JzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWZlcmVuY2UgPSAiYXppbXV0aF9yZWZlcmVuY2UvYm9uZW1hcnJvd3JlZi5TZXVyYXREYXRhL2luc3QvYXppbXV0aC8iKQpgYGAKCkFkZCBBemltdXRoIGxhYmVscyB0byBvcmlnaW5hbCBzZXVyYXQgb2JqZWN0LgoKYGBge3IsIGV2YWw9Rn0KZ2FsZW5fYW1sX2Rvbm9ycyA8LQogIEFkZE1ldGFEYXRhKAogICAgZ2FsZW5fYW1sX2Rvbm9ycywKICAgIGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aEBtZXRhLmRhdGEsCiAgICBjb2wubmFtZSA9IGMoInByZWRpY3RlZC5jZWxsdHlwZS5sMSIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiKQogICkKYGBgCgoKVGhlIGZ1bmN0aW9uIHJldHVybnMgY2VsbCB0eXBlIGxhYmVscyBvbiAyIGxldmVscywgYW5kIDIgUUMgbWV0cmljcywgYSBtYXBwaW5nIHNjb3JlIGFuZCBhIHByZWRpY3Rpb24gc2NvcmUgZm9yIGVhY2ggbGV2ZWwuICAKSXQgYWxzbyByZXR1cm5zIHRoZSBwcm9qZWN0aW9uIG9mIHRoZSBjZWxscyBvbnRvIHRoZSByZWZlcmVuY2UgVU1BUCBzcGFjZS4KCmBgYHtyIGZpZy5oZWlnaHQ9NywgZXZhbD1GfQpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aCwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmNlbGx0eXBlLmwxIiwgc2h1ZmZsZSA9IFQsIGxhYmVsID0gVCkgKyBjb29yZF9lcXVhbCgpCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aCwgZmVhdHVyZXMgPSAicHJlZGljdGVkLmNlbGx0eXBlLmwxLnNjb3JlIikgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCBzaHVmZmxlID0gVCwgbGFiZWwgPSBUKSArIGNvb3JkX2VxdWFsKCkKRmVhdHVyZVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBmZWF0dXJlcyA9ICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIuc2NvcmUiKSArIGNvb3JkX2VxdWFsKCkKRmVhdHVyZVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBmZWF0dXJlcyA9ICJtYXBwaW5nLnNjb3JlIikgKyBjb29yZF9lcXVhbCgpCgpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aCwgZ3JvdXAuYnkgPSAiU2FtcGxlQ2xhc3MiLCBzaHVmZmxlID0gVCkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBncm91cC5ieSA9ICJDZWxsVHlwZSIsIHNodWZmbGUgPSBULCBsYWJlbCA9IFQpICsgY29vcmRfZXF1YWwoKQpgYGAKCgpDb21wYXJlIEF6aW11dGggYW5ub3RhdGlvbnMgd2l0aCBiYWNrc3BpbiBjbHVzdGVyIGFubm90YXRpb24sIGZvbGxvd2VkIGJ5IFJGLgpBbHNvIGNvbXBhcmUgd2l0aCBtYWxpZ25hbnQvbm9uIG1hbGlnbmFudCBSRiBhbm5vdGF0aW9uLiAKCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZXZhbD1GfQpoZWF0bWFwKHRhYmxlKGdhbGVuX2FtbF9kb25vcnMkYmFja3NwaW5fY2VsbHR5cGUsIGdhbGVuX2FtbF9kb25vcnMkcHJlZGljdGVkLmNlbGx0eXBlLmwxKSwgc2NhbGUgPSBOVUxMLCBSb3d2ID0gTkEsIENvbHYgPSBOQSkKaGVhdG1hcCh0YWJsZShnYWxlbl9hbWxfZG9ub3JzJGJhY2tzcGluX2NlbGx0eXBlLCBnYWxlbl9hbWxfZG9ub3JzJHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIHNjYWxlID0gTlVMTCwgUm93diA9IE5BLCBDb2x2ID0gTkEpCgpoZWF0bWFwKHRhYmxlKGdhbGVuX2FtbF9kb25vcnMkQ2VsbFR5cGUsIGdhbGVuX2FtbF9kb25vcnMkcHJlZGljdGVkLmNlbGx0eXBlLmwxKSwgc2NhbGUgPSBOVUxMLCBSb3d2ID0gTkEsIENvbHYgPSBOQSkKaGVhdG1hcCh0YWJsZShnYWxlbl9hbWxfZG9ub3JzJENlbGxUeXBlLCBnYWxlbl9hbWxfZG9ub3JzJHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIHNjYWxlID0gTlVMTCwgUm93diA9IE5BLCBDb2x2ID0gTkEpCmBgYAoKSW4gdGhlIGVuZCwgSSByZWFsaXplZCB0aGF0IHRoZSBBemltdXRoIGFubm90YXRpb25zIGFyZSBwcm9ibGVtYXRpYywgYmVjYXVlIGVpdGhlciB2ZXJ5IGdyYW51bGFyIG9yIHVzZWxlc3MgYmVjYXVzZSBsdW1waW5nIGV2ZXJ5dGhpbmcgaW50byBIU1BDLiAKCgojIyMgVHJhaW4gY2xhc3NpZmllcgoKYmFja3NwaW4gY2VsbHR5cGUgYW5ub3RhdGlvbiBpcyBhdmFpbGFibGUgZm9yIDc4MyBjZWxscyBvZiAxLDU5MCBCTTUgQ0QzNCtDRDM44oCTLgoKYGBge3J9CnkgPC0gZmFjdG9yKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSRiYWNrc3Bpbl9jZWxsdHlwZSkKWCA8LSBnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHlAYXNzYXlzJFJOQUBsYXllcnMkZGF0YQpYIDwtIFhbLCFpcy5uYSh5KV0Kcm93bmFtZXMoWCkgPC0gcm93bmFtZXMoZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5KQp5IDwtIHlbIWlzLm5hKHkpXQoKIyAjIGNvbWJpbmUgSFNDIGFuZCBQcm9nCiMgeVt5ICVpbiUgYygiSFNDIiwgIlByb2ciKV0gPC0gIkhTQ19Qcm9nXyIKYGBgCgpWYW4gR2FsZW4gZXQgYWwuIHNlbGVjdCBnZW5lcyB3aXRoIGF2ZXJhZ2Ugbm9ybWFsaXplZCBleHByZXNzaW9uID49IDAuMDEgKG5vcm1hbGl6ZWQgdG8gMTBrIHJlYWRzKS4KVGhpcyBpcyBiaWFzZWQgYnkgY2xhc3MuIEluc3RlYWQsIHNlbGVjdCBnZW5lcyBleHByZXNzZWQgaW4gNSUgY2VsbHMgaW4gYXQgbGVhc3QgMSBjbGFzcyAoMTAxNDEgZ2VuZXMpLgoKYGBge3J9CiMgbWVhbiBub3JtYWxpemVkIGV4cHJlc3Npb24gPj0gMC4wMQprZWVwLmdlbmVzIDwtIHJvd01lYW5zKGV4cChYKSAtIDEpID49IDAuMDEKdGFibGUoa2VlcC5nZW5lcykKCiMgZXhwcmVzc2VkIGluIDElIGNlbGxzCmtlZXAuZ2VuZXMuZXhwcmVzc2VkIDwtIHJvd1N1bXMoKFggPiAwKSAvIG5jb2woZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5KSkgPj0gMC4wMQp0YWJsZShrZWVwLmdlbmVzLmV4cHJlc3NlZCkKCiMgZXhwcmVzc2VkIGluIDElIGNlbGxzIGluIGF0IGxlYXN0IDEgY2xhc3MKY2xhc3NlcyA8LSB1bmlxdWUoeSkKa2VlcC5nZW5lcy5jbGFzcyA8LSBsYXBwbHkoY2xhc3NlcywgRlVOID0gZnVuY3Rpb24oY2xhc3MpIHsKICBnZW5lcyA8LSBuYW1lcyh3aGljaChyb3dTdW1zKChYWyx5ID09IGNsYXNzXSA+IDApIC8gc3VtKHkgPT0gY2xhc3MpKSA+PSAwLjA1KSkKICByZXR1cm4oZ2VuZXMpCn0pCmtlZXAuZ2VuZXMuY2xhc3MgPC0gcm93bmFtZXMoWCkgJWluJSB1bmxpc3Qoa2VlcC5nZW5lcy5jbGFzcykKdGFibGUoa2VlcC5nZW5lcy5jbGFzcykKClggPC0gWFt3aGljaChrZWVwLmdlbmVzLmNsYXNzKSxdCmBgYAoKCkFsbCBjZWxscyBhcmUgbWFsZS4gUmVtb3ZlIHNleC1zcGVjaWZpYyBnZW5lcyBhbnl3YXlzLiAKCmBgYHtyfQprZWVwLmdlbmVzIDwtICFyb3duYW1lcyhYKSAlaW4lIHNleF9nZW5lcwp0YWJsZShrZWVwLmdlbmVzKQpYIDwtIFhbd2hpY2goa2VlcC5nZW5lcyksXQpgYGAKCgpzYW1wc2l6ZSBpcyB0aGUgbnVtYmVyIG9mIGNlbGxzIHNhbXBsZWQgZnJvbSBlYWNoIGNsYXNzLiBCeSBkZWZhdWx0IGBjZWlsaW5nKC42MzIqbnJvdyh4KSlgLgpUaGlzIGlzIG9uZSB3YXkgdG8gZGVhbCB3aXRoIGltYmFsYW5jZWQgZGF0YSBpbiBSRiBieSB1c2luZyBiYWxhbmNlZCBkYXRhIHNldHMgZm9yIGVhY2ggdHJlZSwgZXZlbiBpZiB0aGUgZGF0YSBpcyBub3QgYmFsYW5jZWQsIHdoaWNoIGlzIG1hZGUgcG9zc2libGUgYnkgYm9vdHN0cmFwLgoKVHJhaW4gMTAwIHRyZWVzIGJlY2F1c2UgSSdtIGltcGF0aWVudC4gCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpyZi5jZWxsdHlwZS5vdXRlciA8LSByYW5kb21Gb3Jlc3QoCiAgeCA9IHQoWCksCiAgeSA9IHksCiAgc2FtcHNpemUgPSByZXAoNTAsIGxlbmd0aCh1bmlxdWUoeSkpKSwKICBudHJlZSA9IDIwMCwKICBkby50cmFjZSA9IEYKKQpgYGAKCmBgYHtyfQpyZi5jZWxsdHlwZS5vdXRlcgpgYGAKClNlbGVjdCAxayBmZWF0dXJlcyBmcm9tIDE0MDc0IGdlbmVzLiAKCmJlc3R2YXIgaXMgdGhlIHZhcmlhYmxlIHVzZWQgdG8gc3BsaXQgdGhlIG5vZGUgKDAgaWYgbm9kZSBpcyB0ZXJtaW5hbCkuCgpgYGB7cn0KcmYuY2VsbHR5cGUub3V0ZXIudXNlZDFrIDwtCiAgbmFtZXMoc29ydCh0YWJsZSgKICAgIHJvd25hbWVzKHJmLmNlbGx0eXBlLm91dGVyJGltcG9ydGFuY2UpW3JmLmNlbGx0eXBlLm91dGVyJGZvcmVzdCRiZXN0dmFyW3JmLmNlbGx0eXBlLm91dGVyJGZvcmVzdCRiZXN0dmFyICE9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDBdXQogICksIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjEwMDBdKQoKcGxvdChyZi5jZWxsdHlwZS5vdXRlciRpbXBvcnRhbmNlW3JmLmNlbGx0eXBlLm91dGVyLnVzZWQxaywgMV0pICAjIGNvcnJlbGF0ZXMgdG8gaW1wb3J0YW5jZSBtZWFzdXJlCmBgYAoKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnJmLmNlbGx0eXBlLmlubmVyIDwtIHJhbmRvbUZvcmVzdCgKICB4ID0gdChYW3JmLmNlbGx0eXBlLm91dGVyLnVzZWQxayxdKSwKICB5ID0geSwKICBzYW1wc2l6ZSA9IHJlcCg1MCwgbGVuZ3RoKHVuaXF1ZSh5KSkpLAogIG50cmVlID0gMjAwLAogIGRvLnRyYWNlID0gRgopCmBgYAoKYGBge3J9CnJmLmNlbGx0eXBlLmlubmVyCmBgYAoKYGBge3J9CnlfcHJlZF9oZWFsdGh5IDwtIHByZWRpY3QocmYuY2VsbHR5cGUuaW5uZXIsIG5ld2RhdGEgPSB0KFgpLCB0eXBlID0gInByb2IiKQpgYGAKCmBgYHtyfQpyZi5jZWxsdHlwZS5pbm5lciRjb25mdXNpb24KYGBgCgplYXJseUVyeSBtaXNjbGFzc2lmaWVkIGFzIGxhdGVFcnkgYW5kIFByb2cuClByb2cgbWlzY2xhc3NpZmllZCBhcyBIU0MuCgpgYGB7cn0KaGVhdG1hcChyZi5jZWxsdHlwZS5pbm5lciRjb25mdXNpb24sIFJvd3YgPSBOQSwgQ29sdiA9IE5BLCBzY2FsZSA9IE5VTEwpCgpyZi5jZWxsdHlwZS5pbm5lciRjb25mdXNpb24gJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiY2xhc3MiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjbGFzcywgeSA9IGNsYXNzLmVycm9yKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKQpgYGAKClByZWRpY3QgY2VsbHR5cGUgb2YgbWFsaWduYW50IEFNTCBjZWxscy4gCgoiSW4gZm91ciBwYXRpZW50cyAoQU1MMzE0LCBBTUwzNzEsIEFNTDcyMkIgYW5kIEFNTDk5NyksIGZvciB3aGljaCB3ZSBkZXRlY3RlZCBmZXcgbXV0YW50IHRyYW5zY3JpcHRzIGFuZCBmZXcgaGlnaCBxdWFsaXR5IGNlbGxzLCB3ZSBjb3VsZCBub3QgY29uZmlkZW50bHkgYXNzaWduIG1hbGlnbmFudCBjZWxscy4iCgpJIGFsc28gZXhjbHVkZSBBTUw0NzUgYW5kIEFNTDQyMEIgYmVjYXVzZSB0aGV5IGFsc28gb25seSBoYXZlIDcgYW5kIDYgY2VsbHMgZ2Vub3R5cGVkIGFzIG1hbGlnbmFudCAoYW5kIHVwb24gZmlyc3QgY2xhc3NpZmljYXRpb24sIHRob3NlIGNlbGxzIHdlcmUgbHltcGhvaWQgY2VsbHMpLgoKYGBge3J9CmdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50IDwtIHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBzdWJzZXQgPSAoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgJiBTYW1wbGVDbGFzcyAhPSAiSGVhbHRoeURvbm9yIikpCnRhYmxlKGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKCnNlbGVjdGVkX3NhbXBsZXMgPC0KICBkb25vcl9tZXRhZGF0YSRTYW1wbGVbIWRvbm9yX21ldGFkYXRhJFNhbXBsZSAlaW4lIGMoIkFNTDMxNCIsICJBTUwzNzEiLCAiQU1MNzIyQiIsICJBTUw5OTciLCAiQU1MNDc1IiwgIkFNTDQyMEIiKV0KZ2FsZW5fYW1sX2Rvbm9yc19tYWxpZ25hbnQgPC0KICBzdWJzZXQoZ2FsZW5fYW1sX2Rvbm9yc19tYWxpZ25hbnQsIFNhbXBsZSAlaW4lIHNlbGVjdGVkX3NhbXBsZXMpCmBgYAoKCmBgYHtyfQpYX21hbGlnbmFudCA8LSBnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudEBhc3NheXMkUk5BQGxheWVycyRkYXRhCnJvd25hbWVzKFhfbWFsaWduYW50KSA8LSByb3duYW1lcyhnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCkKWF9tYWxpZ25hbnQgPC0gWF9tYWxpZ25hbnRbcmYuY2VsbHR5cGUub3V0ZXIudXNlZDFrLF0KCnlfcHJlZF9tYWxpZ25hbnQgPC0gcHJlZGljdChyZi5jZWxsdHlwZS5pbm5lciwgbmV3ZGF0YSA9IHQoWF9tYWxpZ25hbnQpLCB0eXBlID0gInByb2IiKQoKeV9wcmVkX21heCA8LSBjb2xuYW1lcyh5X3ByZWRfbWFsaWduYW50KVttYXguY29sKHlfcHJlZF9tYWxpZ25hbnQpXQpgYGAKCgpUaGVyZSBhcmUgY2VsbHMgY2xhc3NpZmllZCBhcyBseW1waG9jeXRlcywgZGVzcGl0ZSBkZXRlY3Rpb24gb2YgbXV0YXRlZCB0cmFuc2NyaXB0cy4KU2FtZSBmb3IgZWFybHkgZXJ5dGhyb2N5dGVzLCBwREMuIAoKYGBge3J9CnRhYmxlKHlfcHJlZF9tYXgpCnRhYmxlKHlfcHJlZF9tYXgsIGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKdChyb3VuZCh0KAogIHRhYmxlKHlfcHJlZF9tYXgsIGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKKSAvIGFzLnZlY3Rvcihjb2xTdW1zKAogIHRhYmxlKHlfcHJlZF9tYXgsIGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKKSksIDIpKQpgYGAKCgpUaGVyZSBhcmUgc29tZSByYXRoZXIgdW5leHBlY3RlZCBIU1BDIGNlbGwgdHlwZSBwcmVkaWN0aW9uczogZWFybHkgZXJ5dGhyb2lkIGNlbGxzIGFuZCBwREMgY2VsbHMuIFdoaWxlIGx5bXBob2lkIGNlbGwgdHlwZXMgYXJlIHByZXR0eSBzdXJlbHkgYSBtaXNjbGFzc2lmaWNhdGlvbiwgZWFybHkgZXJ5IGFuZCBwREMgY2FuIGFjdHVhbGx5IGJlIG11dGF0ZWQgaW4gQU1MLCBhY2NvcmRpbmcgdG8gbGl0ZXJhdHVyZS4gCgpodHRwczovL3d3dy5tZHBpLmNvbS8yMDcyLTY2OTQvMTQvMTQvMzM3NQpBY3V0ZSBteWVsb2lkIGxldWtlbWlhIChBTUwpIHdpdGgg4omlMiUgcGxhc21hY3l0b2lkIGRlbmRyaXRpYyBjZWxscyAocERDKSBoYXMgYmVlbiByZWNlbnRseSBkZXNjcmliZWQgYXMgQU1MIHdpdGggcERDIGRpZmZlcmVudGlhdGlvbiAocERDLUFNTCkgY2hhcmFjdGVyaXplZCBieSBwREMgZXhwYW5zaW9uIHdpdGggZnJlcXVlbnQgUlVOWDEgbXV0YXRpb25zLgoKQU1MOTIxQSBoYXMgOCUgY2VsbHMgcHJlZGljdGVkIGFzIHBEQyBhbmQgUlVOWDEgTk1fMDAxNzU0IGMuMTY3VD5DIHAuTDU2UyAoNjMuNSUsIFZVUykKCkluIGNvbWJpbmF0aW9uIHdpdGggdGhlIHBvc3NpYmxlIHNpZ25hdHVyZSBjb21iaW5hdGlvbnMgaW4gQU1MIGNlbGxzLCBJIHdhbnQgdG8ga2VlcCB0aG9zZSBwcmVkaWN0aW9ucy4gCgpIb3dldmVyLCB0aGVyZSBhcmUgdG9vIGZldyBjZWxscyB0byB1c2UgdGhlbSBhcyBjbGFzc2VzIGluIGEgY2xhc3NpZmllci4gSW5zdGVhZCwgSSdsbCBpbmNsdWRlIHRoZW0gYXMgZmVhdHVyZXMsIGFsb25nc2lkZSBnZW5lcywgYW5kIHByZWRpY3QgbWFsaWduYW50IHZzLiBub3JtYWwgY2xhc3Nlcy4gCgpJJ2xsIG9ubHkgdHJhaW4gMSBjbGFzc2lmaWVyLCBpbnN0ZWFkIG9mIGlubmVyIGFuZCBvdXRlci4gClRoaXMgd2F5LCB2YWxpZGF0aW9uIG9mIGNvcnJlY3QgcHJlZGljdGlvbiBvZiBtYWxpZ25hbnQgY2VsbHMgaXMgbW9yZSB0cnV0aGZ1bC4gCgpgYGB7cn0KWF9oZWFsdGh5IDwtIGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeUBhc3NheXMkUk5BQGxheWVycyRkYXRhClhfaGVhbHRoeSA8LSBYX2hlYWx0aHlbLCFpcy5uYShnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkkYmFja3NwaW5fY2VsbHR5cGUpXQpyb3duYW1lcyhYX2hlYWx0aHkpIDwtIHJvd25hbWVzKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSkKIyBjb21iaW5lIGNlbGx0eXBlIHByZWRpY3Rpb24gd2l0aCBnZW5lIGV4cHJlc3Npb24KWF9oZWFsdGh5X2NlbGx0eXBlIDwtIHJiaW5kKFhfaGVhbHRoeSwgdCh5X3ByZWRfaGVhbHRoeSkpCgpYX21hbGlnbmFudCA8LSBnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudEBhc3NheXMkUk5BQGxheWVycyRkYXRhCnJvd25hbWVzKFhfbWFsaWduYW50KSA8LSByb3duYW1lcyhnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCkKIyBjb21iaW5lIGNlbGx0eXBlIHByZWRpY3Rpb24gd2l0aCBnZW5lIGV4cHJlc3Npb24KWF9tYWxpZ25hbnRfY2VsbHR5cGUgPC0gcmJpbmQoWF9tYWxpZ25hbnQsIHQoeV9wcmVkX21hbGlnbmFudCkpCgpYX2NlbGx0eXBlIDwtIGNiaW5kKFhfaGVhbHRoeV9jZWxsdHlwZSwgWF9tYWxpZ25hbnRfY2VsbHR5cGUpCmRpbShYX2NlbGx0eXBlKQoKWCA8LSBjYmluZChYX2hlYWx0aHksIFhfbWFsaWduYW50KQpkaW0oWCkKCnkgPSBmYWN0b3IoYyhyZXAoImhlYWx0aHkiLCBuY29sKFhfaGVhbHRoeSkpLCByZXAoIm1hbGlnbmFudCIsIG5jb2woWF9tYWxpZ25hbnQpKSkpCnRhYmxlKHkpCmBgYAoKS2VlcCBjZWxscyBleHByZXNzZWQgaW4gPj0gMSUgaW4gYXQgbGVhc3Qgb25lIG9mIHRoZSBjbGFzc2VzLiAKCmBgYHtyfQprZWVwLmdlbmVzLmV4cHJlc3NlZCA8LSByb3dTdW1zKChYID4gMCkgLyBuY29sKFgpKSA+PSAwLjAxCnRhYmxlKGtlZXAuZ2VuZXMuZXhwcmVzc2VkKQoKIyBleHByZXNzZWQgaW4gMSUgY2VsbHMgaW4gYXQgbGVhc3QgMSBjbGFzcwpjbGFzc2VzIDwtIHVuaXF1ZSh5KQprZWVwLmdlbmVzLmNsYXNzIDwtIGxhcHBseShjbGFzc2VzLCBGVU4gPSBmdW5jdGlvbihjbGFzcykgewogIGdlbmVzIDwtIG5hbWVzKHdoaWNoKHJvd1N1bXMoKFhbLHkgPT0gY2xhc3NdID4gMCkgLyBzdW0oeSA9PSBjbGFzcykpID49IDAuMDEpKQogIHJldHVybihnZW5lcykKfSkKa2VlcC5nZW5lcy5jbGFzcyA8LSByb3duYW1lcyhYKSAlaW4lIHVubGlzdChrZWVwLmdlbmVzLmNsYXNzKQpuYW1lcyhrZWVwLmdlbmVzLmNsYXNzKSA8LSByb3duYW1lcyhYKQp0YWJsZShrZWVwLmdlbmVzLmNsYXNzKQpgYGAKCmBgYHtyfQpYIDwtIFhbbmFtZXMod2hpY2goa2VlcC5nZW5lcy5jbGFzcykpLF0KWF9jZWxsdHlwZSA8LSBYX2NlbGx0eXBlW2MobmFtZXMod2hpY2goa2VlcC5nZW5lcy5jbGFzcykpLCBjb2xuYW1lcyh5X3ByZWRfbWFsaWduYW50KSksIF0KWF9jZWxsdHlwZV9kcml2ZXIgPC0gWF9jZWxsdHlwZVshcm93bmFtZXMoWF9jZWxsdHlwZSkgJWluJSBnZW5lc19nZW5vdHlwZXMsIF0KYGBgCgpUcmFpbiAxIGNsYXNzaWZpZXIgd2l0aCBjZWxsdHlwZSBwcm9iYWJpbGl0aWVzIGFzIGZlYXR1cmVzIGFuZCBvbmUgd2l0aG91dCBhbmQgb25lIHdpdGhvdXQgZ2VuZXMgd2l0aCBkcml2ZXIgbXV0YXRpb25zLiAKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnJmLm1hbGlnbmFudCA8LSByYW5kb21Gb3Jlc3QoCiAgeCA9IHQoWCksCiAgeSA9IHksCiAgc2FtcHNpemUgPSByZXAoMjAwLCBsZW5ndGgodW5pcXVlKHkpKSksCiAgbnRyZWUgPSAyMDAsCiAgZG8udHJhY2UgPSBGCikKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpyZi5tYWxpZ25hbnQuY2VsbHR5cGUgPC0gcmFuZG9tRm9yZXN0KAogIHggPSB0KFhfY2VsbHR5cGUpLAogIHkgPSB5LAogIHNhbXBzaXplID0gcmVwKDIwMCwgbGVuZ3RoKHVuaXF1ZSh5KSkpLAogIG50cmVlID0gMjAwLAogIGRvLnRyYWNlID0gRgopCmBgYAoKYGBge3J9CnNldC5zZWVkKDEyMykKcmYubWFsaWduYW50LmNlbGx0eXBlLmRyaXZlciA8LSByYW5kb21Gb3Jlc3QoCiAgeCA9IHQoWF9jZWxsdHlwZV9kcml2ZXIpLAogIHkgPSB5LAogIHNhbXBzaXplID0gcmVwKDIwMCwgbGVuZ3RoKHVuaXF1ZSh5KSkpLAogIG50cmVlID0gMjAwLAogIGRvLnRyYWNlID0gRgopCmBgYAoKSW5jbHVkaW5nIGNlbGwgdHlwZSBwcm9iYWJpbGl0aWVzIGdpdmVzIGEgCgpgYGB7cn0KcmYubWFsaWduYW50CnJmLm1hbGlnbmFudC5jZWxsdHlwZQpyZi5tYWxpZ25hbnQuY2VsbHR5cGUuZHJpdmVyCmBgYAoKYGBge3J9CnlfbWFsaWduYW50X2NlbGx0eXBlX3ByZWQgPC0gcHJlZGljdChyZi5tYWxpZ25hbnQuY2VsbHR5cGUsIG5ld2RhdGEgPSB0KFhfY2VsbHR5cGUpLCB0eXBlID0gInByb2IiKQpgYGAKCmBgYHtyfQp5X21hbGlnbmFudF9jZWxsdHlwZV9wcmVkX21heCA8LSBjb2xuYW1lcyh5X21hbGlnbmFudF9jZWxsdHlwZV9wcmVkKVttYXguY29sKHlfbWFsaWduYW50X2NlbGx0eXBlX3ByZWQpXQoKZG9ub3JfbGlzdCA8LSB1bmlxdWUoZ2FsZW5fYW1sX2Rvbm9yc19tYWxpZ25hbnQkU2FtcGxlKQpuYW1lcyhkb25vcl9saXN0KSA8LSBkb25vcl9saXN0CgojIGRvbm9yX2FjY3VyYWN5X3JmLm1hbGlnbmFudC5jZWxsdHlwZSA8LSBsYXBwbHkoZG9ub3JfbGlzdCwgZnVuY3Rpb24oZG9ub3IpIHN1bSh5X21hbGlnbmFudF9jZWxsdHlwZV9wcmVkX21heFtkb25vcnMgPT0gZG9ub3JdID09ICJtYWxpZ25hbnQiKSAvIHN1bShkb25vcnMgPT0gZG9ub3IpKQojIGRvbm9yX2FjY3VyYWN5X3JmLm1hbGlnbmFudC5jZWxsdHlwZQpgYGAKCgojIyMgTW9kZWwgZXZhbHVhdGlvbgoKIyMjIyBDcm9zcy12YWxpZGF0aW9uIG9uIEFNTCBkb25vcnMKClBpY2sgNCBkb25vcnMgYXQgcmFuZG9tICh0byBzYXZlIHRpbWUpLgoKQWNjdXJhY3kgaXMgaG93IG1hbnkgY2VsbHMgYXJlIGNvcnJlY3RseSBjbGFzc2lmaWVkIGFzIG1hbGlnbmFudC4gCgpXaXRoIHRoaXMgY3Jvc3MgdmFsaWRhdGlvbiwgd2UgY291bGQgZG8gbW9yZSBwYXJhbXRlciB0ZXN0aW5nLiBMaWtlIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgdG8gcGljayBmb3IgdGhlIGlubmVyIGNlbGwgdHlwZSBjbGFzc2lmaWVyLgpPciBob3cgbWFueSBwYXJhbXRlcnMgdG8gc2FtcGxlIGZvciBlYWNoIGRlY2lzaW9uIGluIHRoZSB0cmVlLiBPciB0aGUgZGVwdGggb2YgdGhlIHRyZWVzLiAKCmBgYHtyfQpkb25vcnMgPC0gYyhnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkkU2FtcGxlLCBnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCRTYW1wbGUpCnRhYmxlKGRvbm9ycykKZG9ub3JzX2N2IDwtIHVuaXF1ZShnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCRTYW1wbGUsIGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSRTYW1wbGUpCnNldC5zZWVkKDEyMykKZG9ub3JzX2N2IDwtIHNhbXBsZShkb25vcnNfY3YsIHNpemUgPSA0KQpkb25vcnNfY3YKYGBgCgpgYGB7cn0KY3ZfcmVzdWx0cyA8LSBsYXBwbHkoZG9ub3JzX2N2LCBmdW5jdGlvbihkb25vcikgewogIGNhdChkb25vcikKICBjYXQoIlxuIikKICAKICAjIHRyYWluIGFuZCB2YWxpZGF0aW9uIHNwbGl0CiAgWF9jZWxsdHlwZV9jdl90cmFpbiA8LSBYX2NlbGx0eXBlWywgZG9ub3JzICE9IGRvbm9yXQogIFhfY2VsbHR5cGVfY3ZfdGVzdCA8LSBYX2NlbGx0eXBlWywgZG9ub3JzID09IGRvbm9yXQogIHlfY3ZfdHJhaW4gPC0geVtkb25vcnMgIT0gZG9ub3JdCiAgeV9jdl90ZXN0IDwtIHlbZG9ub3JzID09IGRvbm9yXQogIAogICMgdHJhaW4gb24gdGVzdCBkYXRhCiAgc2V0LnNlZWQoMTIzKQogIHJmLm1hbGlnbmFudC5jZWxsdHlwZS5jdiA8LSByYW5kb21Gb3Jlc3QoCiAgICB4ID0gdChYX2NlbGx0eXBlX2N2X3RyYWluKSwKICAgIHkgPSB5X2N2X3RyYWluLAogICAgc2FtcHNpemUgPSByZXAoMjAwLCBsZW5ndGgodW5pcXVlKHlfY3ZfdHJhaW4pKSksCiAgICBudHJlZSA9IDIwMCwKICAgIGRvLnRyYWNlID0gRkFMU0UKICApCiAgCiAgIyBwcmVkaWN0IHZhbGlkYXRpb24gZGF0YQogIHlfY3ZfcHJlZCA8LQogICAgcHJlZGljdCgKICAgICAgcmYubWFsaWduYW50LmNlbGx0eXBlLmN2LAogICAgICBuZXdkYXRhID0gdChYX2NlbGx0eXBlX2N2X3Rlc3QpLAogICAgICB0eXBlID0gInByb2IiCiAgICApCiAgeV9jdl9wcmVkX21heCA8LSBjb2xuYW1lcyh5X2N2X3ByZWQpW21heC5jb2woeV9jdl9wcmVkKV0KICAKICAjIGNhbGN1bGF0ZSBhY2N1cmFjeQogIGFjY3VyYWN5IDwtIHN1bSh5X2N2X3ByZWRfbWF4ID09IHlfY3ZfdGVzdCkgLyBsZW5ndGgoeV9jdl9wcmVkX21heCkKICByZXR1cm4oYWNjdXJhY3kpCn0pCgpuYW1lcyhjdl9yZXN1bHRzKSA8LSBkb25vcnNfY3YKYGBgCgpDcm9zcyB2YWxpZGF0aW9uIHdoZW4gbGVhdmluZyBvdXQgZHJpdmVyIG11dGF0aW9uIGdlbmVzLiAKCmBgYHtyfQpjdl9yZXN1bHRzX2RyaXZlciA8LSBsYXBwbHkoZG9ub3JzX2N2LCBmdW5jdGlvbihkb25vcikgewogIGNhdChkb25vcikKICBjYXQoIlxuIikKICAKICAjIHRyYWluIGFuZCB2YWxpZGF0aW9uIHNwbGl0CiAgWF9jZWxsdHlwZV9jdl90cmFpbiA8LSBYX2NlbGx0eXBlX2RyaXZlclssIGRvbm9ycyAhPSBkb25vcl0KICBYX2NlbGx0eXBlX2N2X3Rlc3QgPC0gWF9jZWxsdHlwZV9kcml2ZXJbLCBkb25vcnMgPT0gZG9ub3JdCiAgeV9jdl90cmFpbiA8LSB5W2Rvbm9ycyAhPSBkb25vcl0KICB5X2N2X3Rlc3QgPC0geVtkb25vcnMgPT0gZG9ub3JdCiAgCiAgIyB0cmFpbiBvbiB0ZXN0IGRhdGEKICBzZXQuc2VlZCgxMjMpCiAgcmYubWFsaWduYW50LmNlbGx0eXBlLmN2IDwtIHJhbmRvbUZvcmVzdCgKICAgIHggPSB0KFhfY2VsbHR5cGVfY3ZfdHJhaW4pLAogICAgeSA9IHlfY3ZfdHJhaW4sCiAgICBzYW1wc2l6ZSA9IHJlcCgyMDAsIGxlbmd0aCh1bmlxdWUoeV9jdl90cmFpbikpKSwKICAgIG50cmVlID0gMjAwLAogICAgZG8udHJhY2UgPSBGQUxTRQogICkKICAKICAjIHByZWRpY3QgdmFsaWRhdGlvbiBkYXRhCiAgeV9jdl9wcmVkIDwtCiAgICBwcmVkaWN0KAogICAgICByZi5tYWxpZ25hbnQuY2VsbHR5cGUuY3YsCiAgICAgIG5ld2RhdGEgPSB0KFhfY2VsbHR5cGVfY3ZfdGVzdCksCiAgICAgIHR5cGUgPSAicHJvYiIKICAgICkKICB5X2N2X3ByZWRfbWF4IDwtIGNvbG5hbWVzKHlfY3ZfcHJlZClbbWF4LmNvbCh5X2N2X3ByZWQpXQogIAogICMgY2FsY3VsYXRlIGFjY3VyYWN5CiAgYWNjdXJhY3kgPC0gc3VtKHlfY3ZfcHJlZF9tYXggPT0geV9jdl90ZXN0KSAvIGxlbmd0aCh5X2N2X3ByZWRfbWF4KQogIHJldHVybihhY2N1cmFjeSkKfSkKCm5hbWVzKGN2X3Jlc3VsdHNfZHJpdmVyKSA8LSBkb25vcnNfY3YKYGBgCgpgYGB7cn0KZG9ub3JfbWV0YWRhdGEgJT4lCiAgZmlsdGVyKFNhbXBsZSAlaW4lIGRvbm9yc19jdiwgYERheXMgZnJvbSBkaWFnbm9zaXNgID09ICJEMCIpICU+JQogIHNlbGVjdChTYW1wbGUsIGBSSFAgTXV0YXRpb25zYCwgYENvbW1vbiB0cmFuc2xvY2F0aW9uYCkgJT4lCiAgdW5pcXVlKCkKYGBgCgpgYGB7cn0KbGFwcGx5KGN2X3Jlc3VsdHMsIHJvdW5kLCAzKQpgYGAKCmBgYHtyfQpsYXBwbHkoY3ZfcmVzdWx0c19kcml2ZXIsIHJvdW5kLCAzKQpgYGAKCgpFeGNsdWRpbmcgZ2VuZXMgd2l0aCBkcml2ZXIgbXV0YXRpb25zIGluY3JlYXNlcyBwYXRpZW50IENWIGFjY3VyYWN5IHNsaWdodGx5IGluIDMvNCBjYXNlcy4KCgoKIyMjIyBDcm9zcy12YWxpZGF0aW9uIG9uIGRyaXZlciBtdXRhdGlvbnMKClRPRE8KClRvIHRlc3QgdGhlIGdlbmVyYWxpemFiaWxpdHkgb2YgdGhlIG1vZGVsIG9uIHVuc2VlbiBtdXRhdGlvbnMsIGNyb3NzIHZhbGlkYXRpb24gc2hvdWxkIGJlIHBlcmZvcm1lZCB3aXRoIGhvbGRpbmcgb3V0IGFsbCBzYW1wbGVzIHdpdGggYSBzcGVjaWZpYyBkcml2ZXIgbXV0YXRpb24uIAoKIlRvIGV4Y2x1ZGUgdGhlIHBvc3NpYmlsaXR5IHRoYXQgdGhlIGhpZ2ggZnJlcXVlbmN5IG9mIGNlbGxzIHdpdGggZGV0ZWN0ZWQgTlBNMSBtdXRhdGlvbnMgYWZmZWN0ZWQgdGhlIGNsYXNzaWZpZXIsIHdlIGdlbmVyYXRlZCBhIHNlcGFyYXRlIGNsYXNzaWZpZXIgdGhhdCBkb2VzIG5vdCBjb25zaWRlciBOUE0xIG11dGFudCBjYWxscy4gVGhpcyBzZXBhcmF0ZSBjbGFzc2lmaWVyIGhhZCBlcXVhbGx5IGhpZ2ggc3BlY2lmaWNpdHkgKDk5LjglIG9mIG5vcm1hbCBjZWxscyBjb3JyZWN0bHkgY2FsbGVkIG5vcm1hbCksIGFuZCBzZW5zaXRpdml0eSAoOTMlIG9mIG1hbGlnbmFudCBjZWxscyBjb3JyZWN0bHkgY2FsbGVkIG1hbGlnbmFudCkgaW4gNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24uIEl0IGlzIGFsc28gY29uc2lzdGVudCB3aXRoIHRoZSBvcmlnaW5hbCBjbGFzc2lmaWVyOiA5NyUgb2YgY2VsbHMgb3JpZ2luYWxseSBjbGFzc2lmaWVkIGFzIG5vcm1hbCB3ZXJlIGNsYXNzaWZpZWQgYXMgbm9ybWFsOyA5MSUgb2YgY2VsbHMgb3JpZ2luYWxseSBjbGFzc2lmaWVkIGFzIG1hbGlnbmFudCB3ZXJlIGNsYXNzaWZpZWQgYXMgbWFsaWduYW50LiBUaGVzZSByZXN1bHRzIGluZGljYXRlIHRoYXQgdGhlIGNsYXNzaWZpZXIgaXMgcm9idXN0IHRvIHRoZSBmcmVxdWVuY3kgb2YgTlBNMSBtdXRhdGlvbnMgaW4gdGhlIHRyYWluaW5nIHNldC4iCgpTaG91bGQgaGF2ZSBwdXQgdGhlIE5QTTEgc2FtcGxlIGluIHRoZSBub24tTlBNMSB0cmFpbmVkIGNsYXNzaWZpZXIuCgojIyMjIEV4dGVybmFsIHRlc3QgZGF0YXNldAoKc2NSTkEtc2VxIGZyb20gQU1MIHBhdGllbnRzIChOUE0xKSB3aXRoIHNjIGdlbm90eXBpbmcuCgpOYWxkaW5pLCBNLk0uLCBDYXNpcmF0aSwgRy4sIEJhcmNlbGxhLCBNLiBldCBhbC4gTG9uZ2l0dWRpbmFsIHNpbmdsZS1jZWxsIHByb2ZpbGluZyBvZiBjaGVtb3RoZXJhcHkgcmVzcG9uc2UgaW4gYWN1dGUgbXllbG9pZCBsZXVrZW1pYS4gTmF0IENvbW11biAxNCwgMTI4NSAoMjAyMykuIFtodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE0NjctMDIzLTM2OTY5LTBdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAyMy0zNjk2OS0wKQoKMTAgcGF0aWVudHMgd2l0aCBOUE0xbXV0IEFNTCwgcHJlc2VudCBpbiB2YW4gR2FsZW4gZGF0YXNldC4KTWFqb3JpdHkgb2YgTlBNMW11dCBBTUwgc2FtcGVscyBpbiB2YW4gR2FsZW4gZGF0YXNldCBhbHNvIGhhdmUgRE5NVDMgbXV0YXRpb24uIEROTVQzIHN0YXR1cyBpcyBub3QgbWVudGlvbmVkIGZvciB0aGlzIHN0dWR5LiAKCjMgcGF0aWVudHMgd2l0aCBkZWwoNykgQU1MLCBub3QgcHJlc2VudCBpbiB2YW4gR2FsZW4gZGF0YS4KCipQcmVwcm9jZXNzaW5nIGluZm9ybWF0aW9uKgpGZWF0dXJlLWJhcmNvZGVzIGZpbHRlcmVkIG1hdHJpY2VzIGZyb20gQ2VsbCBSYW5nZXIgd2VyZSB1c2VkIGFzIGlucHV0IGZvciBTZXVyYXQgUiBwYWNrYWdlNjQsNjUgKHZlcnNpb24gMy4yLjMpLiBTZXVyYXQgb2JqZWN0cyB3ZXJlIG1lcmdlZCBpbiBhIHNpbmdsZSBmdWxsIGRhdGFzZXQuIENlbGxzIHdpdGggYSBtaXRvY2hvbmRyaWFsIGNvdW50IHJhdGlvIGhpZ2hlciB0aGFuIDAuMiBhbmQgPDEwMCBvciA+NzAwMCBleHByZXNzZWQgZ2VuZXMgd2VyZSByZW1vdmVkIGZyb20gdGhlIGRhdGFzZXQuIFVNSSBjb3VudHMgd2VyZSBsb2cgbm9ybWFsaXplZCBhbmQgc2NhbGVkIGZvciBhIGZhY3RvciBvZiAxMCwwMDAuICAKVGhlIHRvcCAyMCUgbW9zdCB2YXJpYWJsZSBnZW5lcyB3ZXJlIHNlbGVjdGVkIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLiBDZWxsIGN5Y2xlIHNjb3JlcyB3ZXJlIGFzc2lnbmVkIHdpdGggdGhlIENlbGxDeWNsZVNjb3JpbmcgZnVuY3Rpb24gdXNpbmcgYSByZWZlcmVuY2UgZ2VuZSBsaXN0czY2LiBXZSBzY2FsZWQgZGF0YSBhbmQgcmVncmVzc2VkIG91dCB1bndhbnRlZCB2YXJpYWJpbGl0eSBieSBwYXNzaW5nIFVNSSBjb3VudCwgcGVyY2VudCBvZiBtaXRvY2hvbmRyaWFsIGdlbmVzIGFuZCBjZWxsIGN5Y2xlIGRpZmZlcmVuY2UgZGVmaW5lZCB2YXJpYWJsZXMgdG8gdGhlIHZhcnMudG8ucmVncmVzcyBhcmd1bWVudC4gQ2VsbCBjeWNsZSBkaWZmZXJlbmNlIHdhcyBkZWZpbmVkIGFzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gUyBwaGFzZSBhbmQgRy8yIE0gcGhhc2UgbW9kdWxlIHNjb3Jlcy4gRG93bnN0cmVhbSBhbmFseXNpcyB3YXMgcGVyZm9ybWVkIG9uIHRoZSB0b3AgMTAwIHByaW5jaXBhbCBjb21wb25lbnRzIChQQ0EpLiBJbiBvcmRlciB0byByZWR1Y2UgcGF0aWVudC1yZWxhdGVkIGFuZCAxMHggY2hlbWlzdHJ5IHZlcnNpb24gKHYyIHZzIHYzKSBiaWFzLCB3ZSBwZXJmb3JtZWQgZGF0YSBpbnRlZ3JhdGlvbiB1c2luZyB0aGUgSGFybW9ueSBwYWNrYWdlICh2MS4wKTI0LgoKKk11dGF0aW9uIGFubm90YXRpb24gaW5mb3JtYXRpb24qCk5QTTEtTUYgY29uc2lkZXJzIHRoZSBVTUkgY291bnRzIHN1cHBvcnRpbmcgZWl0aGVyIE5QTTEgbXV0YW50IG9yIFdUIGFsbGVsZSB0byBjbGFzc2lmeSBjZWxscyBhcyAKTVVUICjiiaUxIFVNSSBmb3IgdGhlIG11dGFudCBhbGxlbGUpCldUICg+NSBXVCB0cmFuc2NyaXB0cywgbm8gbXV0YW50IHRyYW5zY3JpcHRzKQpORCDigJMgbm90IGRldGVjdGVkIChjZWxscyB3aXRoIOKJpCA1IFdUIHRyYW5zY3JpcHRzIGFuZCBubyBtdXRhbnQgdHJhbnNjcmlwdHMpCk5vQ2FsbCAoY2VsbHMgd2l0aG91dCBjb3ZlcmFnZSBvdmVyIHRoZSBOUE0xIG11dGF0aW9uIHJlZ2lvbikKCkNoZWNrIHBhdGllbnQgbWV0YWRhdGEuICAKRG8gbm90IHByb3ZpZGUgaW5mb3JtYXRpb24gb24gYWdlLCBTZXggb3IgY2xpbmluY2FsIGJsYXN0IGNvdW50LgoKYGBge3IgcmVzdWx0cz1GfQptZXRhZGF0YV9ucG0xX3BhdGllbnRzIDwtIHJlYWR4bDo6cmVhZF94bHN4KCJuYWxkaW5pX2V0X2FsLzQxNDY3XzIwMjNfMzY5NjlfTU9FU001X0VTTS54bHN4IikKYGBgCgoKYGBge3J9Cm1ldGFkYXRhX25wbTFfcGF0aWVudHMKYGBgCgoKQ2hlY2sgY2VsbCBtZXRhZGF0YS4gIApJIGNhbid0IGZpZ3VyZSBvdXQgd2hhdCB0aGUgb3JpZy5pZGVudCAoZS5nLiBNMDMpIGlzLiBJdCBpcyB0aGUgSUQgdXNlZCBpbiB0aGUgZXhwcmVzc2lvbiBtYXRyaXguIFNvIG1heWJlIHRoZSBjYXB0dXJlLCBidXQgaXQgZG9lc24ndCBtYWtlIG11Y2ggc2Vuc2UgdG8gaGF2ZSA5IGNlbGxzIGZyb20gUFQxMiBhbmQgNTcwIGNlbGxzIGZyb20gUFQxMyBpbiBNNDQuIFdoeSB3b3VsZCB0aGVyZSBiZSBsZWFrYWdlIGJldHdlZW4gY2FwdHVyZXMuIAoKUFQwMSwgUFQwMiwgUFQxMyBhcmUgcHJpbWFyeSByZWZyYWN0b3J5LiAKUFQwNiwgUFQwNywgUFQxMiBhcmUgbG9uZy10ZXJtIGNvbXBsZXRlIHJlbWlzc2lvbi4KUFQwOCwgUFQwOSwgUFQxMCwgUFQxNSBhcmUgTlBNMW11dCBBTUwgcmVsYXBzZSBwb3N0LWNoZW1vdGhlcmFweS4KCmBgYHtyfQptZXRhZGF0YV9ucG0xX2NlbGxzIDwtIHJlYWRSRFMoIm5hbGRpbmlfZXRfYWwvR1NFMTg1OTkxX0Z1bGxfUGF0aWVudF9tZXRhZGF0YS5yZHMiKQpoZWFkKG1ldGFkYXRhX25wbTFfY2VsbHMpCmBgYAoKQ2hlY2sgbnVtYmVyIG9mIGdlbm90eXBlZCBjZWxscyBwZXIgc2FtcGxlIHRvIHBpY2sgb25lIGZvciB0ZXN0aW5nLiAKClBUMDIsIGNvbWJpbmluZyBkaWFnbm9zaXMgYW5kIEQzMCBzYW1wbGVzLCBoYXMgYSBsb3Qgb2YgV1QgYW5kIE1VVCBjZWxscywgTlBNMW11dCwgcHJpbWFyeSByZWZyYWN0b3J5LiAKR29vZCBwYXRpZW50IHRvIHRlc3QgaWYgY2xhc3NpZmllciBjYW4gZXh0cmFwb2xhdGUgdG8gb3RoZXIgc2NSTkEtc2VxIGRhdGFzZXRzLgoKYGBge3J9Cm1ldGFkYXRhX25wbTFfcGF0aWVudHMgJT4lCiAgbXV0YXRlKFNhbXBsZSA9IHBhc3RlKFBhdGllbnRJRCwgVGltZXBvaW50LCBzZXAgPSAiXyIpKSAlPiUKICBncm91cF9ieShTYW1wbGUsIENsYXNzaWZpY2F0aW9uKSAlPiUKICBzdW1tYXJpemUobl9jZWxscyA9IHN1bShuKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gU2FtcGxlLCB5ID0gbl9jZWxscywgZmlsbCA9IENsYXNzaWZpY2F0aW9uKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgpDYXB0dXJlcyB3aXRoIFBUMDIgZGF0YS4KTm90IHN1cmUgd2h5IHRoZXJlIGFyZSBvbmx5IDkgY2VsbHMgaW4gdGhlIG90aGVyIGNhcHR1cmUuIAoKYGBge3J9Cm1ldGFkYXRhX25wbTFfY2VsbHMgJT4lCiAgZmlsdGVyKFBhdGllbnRJRCA9PSAiUFQwMiIpICU+JQogIHB1bGwob3JpZy5pZGVudCkgJT4lCiAgdGFibGUoKQoKbWV0YWRhdGFfbnBtMV9jZWxscyAlPiUKICBmaWx0ZXIoUGF0aWVudElEID09ICJQVDA3IikgJT4lCiAgcHVsbChvcmlnLmlkZW50KSAlPiUKICB0YWJsZSgpCmBgYAoKUmVhZCBpbiBjb3VudCBkYXRhLgoKYGBge3J9Cm5hbGRpbmlfc2V1cmF0X20yNSA8LSBSZWFkMTBYKAogIGRhdGEuZGlyID0gIm5hbGRpbmlfZXRfYWwvR1NFMTg1OTkxX1JBVy9NMjUvIiwKICBnZW5lLmNvbHVtbiA9IDIsCiAgY2VsbC5jb2x1bW4gPSAxLAogIHVuaXF1ZS5mZWF0dXJlcyA9IFRSVUUsCiAgc3RyaXAuc3VmZml4ID0gRkFMU0UKKQpjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjUpIDwtIHBhc3RlMCgiTTI1XyIsIGdzdWIoIi0xIiwgIiIsIGNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yNSkpKQoKbmFsZGluaV9zZXVyYXRfbTI2IDwtIFJlYWQxMFgoCiAgZGF0YS5kaXIgPSAibmFsZGluaV9ldF9hbC9HU0UxODU5OTFfUkFXL00yNi8iLAogIGdlbmUuY29sdW1uID0gMiwKICBjZWxsLmNvbHVtbiA9IDEsCiAgdW5pcXVlLmZlYXR1cmVzID0gVFJVRSwKICBzdHJpcC5zdWZmaXggPSBGQUxTRQopCmNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yNikgPC0gcGFzdGUwKCJNMjZfIiwgZ3N1YigiLTEiLCAiIiwgY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTI2KSkpCgojIGNvbWJpbmUgY2FwdHVyZXMgYW5kIG1lcmdlCm5hbGRpbmlfc2V1cmF0X3B0MDIgPC0gY2JpbmQobmFsZGluaV9zZXVyYXRfbTI1LCBuYWxkaW5pX3NldXJhdF9tMjYpCm5hbGRpbmlfc2V1cmF0X3B0MDIgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IG5hbGRpbmlfc2V1cmF0X3B0MDIsIG1pbi5jZWxscyA9IDAsIG1pbi5mZWF0dXJlcyA9IDIwMCkKbmNvbChuYWxkaW5pX3NldXJhdF9wdDAyKQoKbmFsZGluaV9zZXVyYXRfbTI1IDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTI2IDwtIE5VTEwKZ2MoKQoKIyBzdWJzZXQgdG8gY2VsbHMgcHJlc2VudCBpbiBtZXRhZGF0YQpuYWxkaW5pX3NldXJhdF9wdDAyIDwtIHN1YnNldChuYWxkaW5pX3NldXJhdF9wdDAyLCBjZWxscyA9IHJvd25hbWVzKG1ldGFkYXRhX25wbTFfY2VsbHMpKQpuY29sKG5hbGRpbmlfc2V1cmF0X3B0MDIpCgojIGFkZCBtZXRhZGF0YSB0byBTZXVyYXQgb2JqZWN0Cm5hbGRpbmlfc2V1cmF0X3B0MDIgPC0gQWRkTWV0YURhdGEobmFsZGluaV9zZXVyYXRfcHQwMiwgbWV0YWRhdGFfbnBtMV9jZWxscykKdGFibGUobmFsZGluaV9zZXVyYXRfcHQwMiRvcmlnLmlkZW50KQpgYGAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9tMjEgPC0gUmVhZDEwWCgKICBkYXRhLmRpciA9ICJuYWxkaW5pX2V0X2FsL0dTRTE4NTk5MV9SQVcvTTIxLyIsCiAgZ2VuZS5jb2x1bW4gPSAyLAogIGNlbGwuY29sdW1uID0gMSwKICB1bmlxdWUuZmVhdHVyZXMgPSBUUlVFLAogIHN0cmlwLnN1ZmZpeCA9IEZBTFNFCikKY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTIxKSA8LSBwYXN0ZTAoIk0yMV8iLCBnc3ViKCItMSIsICIiLCBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjEpKSkKCm5hbGRpbmlfc2V1cmF0X20yMiA8LSBSZWFkMTBYKAogIGRhdGEuZGlyID0gIm5hbGRpbmlfZXRfYWwvR1NFMTg1OTkxX1JBVy9NMjIvIiwKICBnZW5lLmNvbHVtbiA9IDIsCiAgY2VsbC5jb2x1bW4gPSAxLAogIHVuaXF1ZS5mZWF0dXJlcyA9IFRSVUUsCiAgc3RyaXAuc3VmZml4ID0gRkFMU0UKKQpjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjIpIDwtIHBhc3RlMCgiTTIyXyIsIGdzdWIoIi0xIiwgIiIsIGNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yMikpKQoKbmFsZGluaV9zZXVyYXRfbTIzIDwtIFJlYWQxMFgoCiAgZGF0YS5kaXIgPSAibmFsZGluaV9ldF9hbC9HU0UxODU5OTFfUkFXL00yMy8iLAogIGdlbmUuY29sdW1uID0gMiwKICBjZWxsLmNvbHVtbiA9IDEsCiAgdW5pcXVlLmZlYXR1cmVzID0gVFJVRSwKICBzdHJpcC5zdWZmaXggPSBGQUxTRQopCmNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yMykgPC0gcGFzdGUwKCJNMjNfIiwgZ3N1YigiLTEiLCAiIiwgY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTIzKSkpCgpuYWxkaW5pX3NldXJhdF9tMjQgPC0gUmVhZDEwWCgKICBkYXRhLmRpciA9ICJuYWxkaW5pX2V0X2FsL0dTRTE4NTk5MV9SQVcvTTI0LyIsCiAgZ2VuZS5jb2x1bW4gPSAyLAogIGNlbGwuY29sdW1uID0gMSwKICB1bmlxdWUuZmVhdHVyZXMgPSBUUlVFLAogIHN0cmlwLnN1ZmZpeCA9IEZBTFNFCikKY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTI0KSA8LSBwYXN0ZTAoIk0yNF8iLCBnc3ViKCItMSIsICIiLCBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjQpKSkKCiMgY29tYmluZSBjYXB0dXJlcyBhbmQgbWVyZ2UKbmFsZGluaV9zZXVyYXRfcHQwNyA8LSBjYmluZChuYWxkaW5pX3NldXJhdF9tMjEsIG5hbGRpbmlfc2V1cmF0X20yMiwgbmFsZGluaV9zZXVyYXRfbTIzLCBuYWxkaW5pX3NldXJhdF9tMjQpCm5hbGRpbmlfc2V1cmF0X3B0MDcgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IG5hbGRpbmlfc2V1cmF0X3B0MDcsIG1pbi5jZWxscyA9IDAsIG1pbi5mZWF0dXJlcyA9IDIwMCkKbmNvbChuYWxkaW5pX3NldXJhdF9wdDA3KQoKbmFsZGluaV9zZXVyYXRfbTIxIDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTIyIDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTIzIDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTI0IDwtIE5VTEwKZ2MoKQoKIyBzdWJzZXQgdG8gY2VsbHMgcHJlc2VudCBpbiBtZXRhZGF0YQpuYWxkaW5pX3NldXJhdF9wdDA3IDwtIHN1YnNldChuYWxkaW5pX3NldXJhdF9wdDA3LCBjZWxscyA9IHJvd25hbWVzKG1ldGFkYXRhX25wbTFfY2VsbHMpKQpuY29sKG5hbGRpbmlfc2V1cmF0X3B0MDcpCgojIGFkZCBtZXRhZGF0YSB0byBTZXVyYXQgb2JqZWN0Cm5hbGRpbmlfc2V1cmF0X3B0MDcgPC0gQWRkTWV0YURhdGEobmFsZGluaV9zZXVyYXRfcHQwNywgbWV0YWRhdGFfbnBtMV9jZWxscykKdGFibGUobmFsZGluaV9zZXVyYXRfcHQwNyRvcmlnLmlkZW50KQpgYGAKCgoKRmlsdGVyIGNlbGxzIGZvciA+MzAwMCBVTUksIDEwMDAgZ2VuZXMsIDwxMCUgbWl0b2Nob25kcmlhbCB0cmFuc2NyaXB0cyBQVDAyLgpQVDA3IHNlZW1zIHRvIGhhdmUgbG93ZXIgcXVhbGl0eS4gVXNlIGxlc3Mgc3RyaW5nZW50IGZpbHRlcnMuIAoKYGBge3J9ClZsblBsb3QobmFsZGluaV9zZXVyYXRfcHQwMiwgZmVhdHVyZXMgPSBjKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIsICJwZXJjZW50Lm10IiksIGxvZyA9IFQsIHB0LnNpemUgPSAwKQpWbG5QbG90KG5hbGRpbmlfc2V1cmF0X3B0MDcsIGZlYXR1cmVzID0gYygibkNvdW50X1JOQSIsICJuRmVhdHVyZV9STkEiLCAicGVyY2VudC5tdCIpLCBsb2cgPSBULCBwdC5zaXplID0gMCkKYGBgCgpTZWVtcyBsaWtlIFBUMDIgaXMgZmVtYWxlLCBQVDA3IGlzIG1hbGUuIAoKYGBge3J9ClZsblBsb3QobmFsZGluaV9zZXVyYXRfcHQwMiwgZmVhdHVyZXMgPSBjKCJYSVNUIikpClZsblBsb3QobmFsZGluaV9zZXVyYXRfcHQwNywgZmVhdHVyZXMgPSBjKCJYSVNUIikpCmBgYAoKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyIDwtCiAgc3Vic2V0KG5hbGRpbmlfc2V1cmF0X3B0MDIsCiAgICAgICAgIHN1YnNldCA9IG5Db3VudF9STkEgPiAzMDAwICYgbkZlYXR1cmVfUk5BID4gMTAwMCAmIHBlcmNlbnQubXQgPCAxMCkKbmFsZGluaV9zZXVyYXRfcHQwNyA8LQogIHN1YnNldChuYWxkaW5pX3NldXJhdF9wdDA3LAogICAgICAgICBzdWJzZXQgPSBuQ291bnRfUk5BID4gMTAwMCAmIG5GZWF0dXJlX1JOQSA+IDUwMCAmIHBlcmNlbnQubXQgPCAxMCkKYGBgCgoKYGBge3J9CnRhYmxlKG5hbGRpbmlfc2V1cmF0X3B0MDIkb3JpZy5pZGVudCkKdGFibGUobmFsZGluaV9zZXVyYXRfcHQwNyRvcmlnLmlkZW50KQpgYGAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcgPC0KICBtZXJnZShuYWxkaW5pX3NldXJhdF9wdDAyLCBuYWxkaW5pX3NldXJhdF9wdDA3KQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcKCm5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwNyA8LQogIG1lcmdlKAogICAgbmFsZGluaV9zZXVyYXRfcHQwMiwKICAgIHkgPSBuYWxkaW5pX3NldXJhdF9wdDA3LAogICAgYWRkLmNlbGwuaWRzID0gYygiUFQwMiIsICJQVDA3IiksCiAgICBwcm9qZWN0ID0gIk5hbGRpbmkiCiAgKQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcKYGBgCgpOb3JtYWxpemUgZ2VuZSBleHByZXNzaW9uLiAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcgPC0gTm9ybWFsaXplRGF0YShuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcpCmBgYAoKCiMjIyMjIFByZWRpY3QgSFNQQyBjZWxsIHR5cGUKCmBgYHtyfQpYIDwtCiAgY2JpbmQoCiAgICBuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdAYXNzYXlzJFJOQUBsYXllcnMkZGF0YS4xLAogICAgbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3QGFzc2F5cyRSTkFAbGF5ZXJzJGRhdGEuMgogICkKcm93bmFtZXMoWCkgPC0gcm93bmFtZXMobmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3KQpgYGAKClNvbWUgZmVhdHVyZXMgdXNlZCBpbiBjbGFzc2lmaWVyIGFyZSBtaXNzaW5nIGluIGRhdGEsIGVpdGhlciBkdWUgdG8gZ2VuZSBJRCBtaXNtYXRjaGVzIG9yIGJlY2F1c2UgdGhlIGdlbmVzIGFyZSBub3QgZXhwcmVzc2VkLiAKQm90aCB2YW4gR2FsZW4gYW5kIE5hbGRpbmkgd2VyZSBhbGlnbmVkIHRvIGhnMzgsIHNvIGl0IGlzIG1vcmUgbGlrZWx5IGxhY2sgb2YgZXhwcmVzc2lvbi4gCldpbGwgc2V0IG1pc3NpbmcgZ2VuZXMgdG8gMC4gCgpgYGB7cn0KZmVhdHVyZXNfbWlzc2luZyA8LQogIHJvd25hbWVzKHJmLmNlbGx0eXBlLmlubmVyJGltcG9ydGFuY2UpWyFyb3duYW1lcyhyZi5jZWxsdHlwZS5pbm5lciRpbXBvcnRhbmNlKSAlaW4lIHJvd25hbWVzKFgpXQoKZmVhdHVyZXNfbWlzc2luZ19tYXQgPC0KICBtYXRyaXgobnJvdyA9IGxlbmd0aChmZWF0dXJlc19taXNzaW5nKSwKICAgICAgICAgbmNvbCA9IG5jb2woWCksCiAgICAgICAgIGRhdGEgPSAwKQpyb3duYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gZmVhdHVyZXNfbWlzc2luZwpjb2xuYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gY29sbmFtZXMoWCkKClggPC0gcmJpbmQoWCwgZmVhdHVyZXNfbWlzc2luZ19tYXQpCgpYIDwtIFhbcm93bmFtZXMocmYuY2VsbHR5cGUuaW5uZXIkaW1wb3J0YW5jZSksIF0KYGBgCgoKYGBge3J9Cm5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19jZWxsdHlwZV9wcmVkIDwtIHByZWRpY3QocmYuY2VsbHR5cGUuaW5uZXIsIG5ld2RhdGEgPSB0KFgpLCB0eXBlID0gInByb2IiKQpgYGAKCiMjIyMjIFByZWRpY3QgbWFsaWduYW50IGNlbGxzCgpgYGB7cn0KWCA8LQogIGNiaW5kKAogICAgbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3QGFzc2F5cyRSTkFAbGF5ZXJzJGRhdGEuMSwKICAgIG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN0Bhc3NheXMkUk5BQGxheWVycyRkYXRhLjIKICApClggPC0gcmJpbmQoWCwgdChuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfY2VsbHR5cGVfcHJlZCkpCnJvd25hbWVzKFgpIDwtCiAgYygKICAgIHJvd25hbWVzKG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwNyksCiAgICBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfY2VsbHR5cGVfcHJlZCkKICApCgpmZWF0dXJlc19taXNzaW5nIDwtCiAgcm93bmFtZXMocmYubWFsaWduYW50LmNlbGx0eXBlJGltcG9ydGFuY2UpWyFyb3duYW1lcyhyZi5tYWxpZ25hbnQuY2VsbHR5cGUkaW1wb3J0YW5jZSkgJWluJSByb3duYW1lcyhYKV0KZmVhdHVyZXNfbWlzc2luZ19tYXQgPC0KICBtYXRyaXgobnJvdyA9IGxlbmd0aChmZWF0dXJlc19taXNzaW5nKSwKICAgICAgICAgbmNvbCA9IG5jb2woWCksCiAgICAgICAgIGRhdGEgPSAwKQpyb3duYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gZmVhdHVyZXNfbWlzc2luZwpjb2xuYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gY29sbmFtZXMoWCkKClggPC0gcmJpbmQoWCwgZmVhdHVyZXNfbWlzc2luZ19tYXQpCmBgYAoKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfbWFsaWduYW50X3ByZWQgPC0KICBwcmVkaWN0KHJmLm1hbGlnbmFudC5jZWxsdHlwZSwKICAgICAgICAgIG5ld2RhdGEgPSB0KFgpLAogICAgICAgICAgdHlwZSA9ICJwcm9iIikKCm5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19tYWxpZ25hbnRfcHJlZF9tYXggPC0KICBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfbWFsaWduYW50X3ByZWQpW21heC5jb2wobmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3X21hbGlnbmFudF9wcmVkKV0KYGBgCgoKIyMjIyMgUmVzdWx0cwoKQ29tcGFyZSBtYWxpZ25hbnQgdnMuIGhlYWx0aHkgcHJlZGljdGlvbiB3aXRoIGdlbm90eXBlIGRhdGEuIAoKYGBge3J9Cm5hbGRpbmlfc2V1cmF0X3B0MDJfbWFsaWduYW50X3ByZWRfbWF4IDwtIG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19tYWxpZ25hbnRfcHJlZF9tYXhbbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3JFBhdGllbnRJRCA9PSAiUFQwMiJdCnRhYmxlKAogIG5hbGRpbmlfc2V1cmF0X3B0MDJfbWFsaWduYW50X3ByZWRfbWF4LAogIG5hbGRpbmlfc2V1cmF0X3B0MDJAbWV0YS5kYXRhJENsYXNzaWZpY2F0aW9uCikKCnJvdW5kKHQodCgKICB0YWJsZSgKICAgIG5hbGRpbmlfc2V1cmF0X3B0MDJfbWFsaWduYW50X3ByZWRfbWF4LAogICAgbmFsZGluaV9zZXVyYXRfcHQwMkBtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24KICApCikgLyBhcy52ZWN0b3IoCiAgdGFibGUobmFsZGluaV9zZXVyYXRfcHQwMkBtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24pCikpLCAzKQpgYGAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDA3X21hbGlnbmFudF9wcmVkX21heCA8LQogIG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19tYWxpZ25hbnRfcHJlZF9tYXhbbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3JFBhdGllbnRJRCA9PSAiUFQwNyJdCnRhYmxlKAogIG5hbGRpbmlfc2V1cmF0X3B0MDdfbWFsaWduYW50X3ByZWRfbWF4LAogIG5hbGRpbmlfc2V1cmF0X3B0MDdAbWV0YS5kYXRhJENsYXNzaWZpY2F0aW9uCikKCnJvdW5kKHQodCgKICB0YWJsZSgKICAgIG5hbGRpbmlfc2V1cmF0X3B0MDdfbWFsaWduYW50X3ByZWRfbWF4LAogICAgbmFsZGluaV9zZXVyYXRfcHQwN0BtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24KICApCikgLyBhcy52ZWN0b3IoCiAgdGFibGUobmFsZGluaV9zZXVyYXRfcHQwN0BtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24pCikpLCAzKQpgYGAKCgpUZXN0IG9uIHNhbXBsZSB3aXRoIGEgbXV0YXRpb24gbm90IHByZXNlbnQgaW4gdmFuIEdhbGVuIGRhdGEuIAoKZGVsKDcpIEFNTDogZGVsZXRpb24gaW4gY2hyb21vc29tZSA3LiAKCkNsYXNzaWZpY2F0aW9uIG9mIGRlbCg3KSBjZWxscyBpbnRvIG1hbGlnbmFudC93dDogIAoiV2UgbGV2ZXJhZ2VkIHRoZSBBZGRNb2R1bGVTY29yZSBmdW5jdGlvbiBmb3IgZXZhbHVhdGluZyB0aGUgZXhwcmVzc2lvbiBsZXZlbCBvZiBhIENociA3IHNpZ25hdHVyZSBieSB1c2luZyBhcyBpbnB1dCBnZW5lIGxpc3QgYWxsIGdlbmVzIGxvY2F0ZWQgb24gaXQuIFRoZSBvYnNlcnZlZCBkaXN0cmlidXRpb24gb2YgQ2hyIDcgbW9kdWxlIHNjb3JlcyBpbiB0aGUgZGF0YXNldHMgZm9sbG93ZWQgYSBiaW1vZGFsIGRpc3RyaWJ1dGlvbiBhbGxvd2luZyB1cyB0byBjbGFzc2lmeSBjZWxscyBhcyBBTUwgb3Igbm9uLUFNTCBieSBydW5uaW5nIGEgay1tZWFucyBjbHVzdGVyaW5nIChuID0gMikgb24gdGhlIHZlY3RvciBvZiBDaHIgNyBzaWduYXR1cmUgc2NvcmVzIGFuZCBsYWJlbGxpbmcgY2VsbHMgaW4gdGhlIGhpZ2ggc2NvcmUgZ3JvdXAgYXMgbm9uLUFNTCBhbmQgdGhvc2UgaW4gdGhlIGxvdyBzY29yZSBncm91cCBhcyBBTUwuIgoKVW5mb3J0dW5hdGVseSwgbm90IGluY2x1ZGVkIGluIG1ldGFkYXRhLgoKUFQxMSwgUFQxNzogcmVmcmFjdG9yeSBkaXNlYXNlOyBQVDE4OiBlYXJseSByZWxhcHNlCgpgYGB7cn0KbWV0YWRhdGFfZGVsN19jZWxscyA8LSByZWFkUkRTKCJuYWxkaW5pX2V0X2FsL0dTRTE4NTk5MV9BTUxfRGVsN19tZXRhZGF0YS5yZHMiKQoKdGFibGUobWV0YWRhdGFfZGVsN19jZWxscyRvcmlnLmlkZW50LCBtZXRhZGF0YV9kZWw3X2NlbGxzJFBhdGllbnRJRCkKYGBgCgpDb21wdXRpbmcgbW9kdWxlIHNjb3JlIHdpdGggYWxsIGdlbmVzIG9uIGNocjcgbWlnaHQgYmUgdmVyeSBzbG93LiAKCkRvd25sb2FkIGNlbGxyYW5nZXIgZ3RmIHJlZmVyZW5jZSwgZ2V0IGFsbCBnZW5lcyBvbiBjaHI3LgoKYGBge3NoIGV2YWw9RkFMU0V9CnNvdXJjZT0iL1VzZXJzL2hvbHplaGVucmlldHRhL0RvY3VtZW50cy9wZXJzb25hbC9waGRfYXBwbGljYXRpb25zL211ZHNfdGFza19tYXJyL3JlZmVyZW5jZV9zb3VyY2VzIgpta2RpciAtcCAiJHNvdXJjZSIKZ3RmX3VybD0iaHR0cDovL2Z0cC5lYmkuYWMudWsvcHViL2RhdGFiYXNlcy9nZW5jb2RlL0dlbmNvZGVfaHVtYW4vcmVsZWFzZV80NC9nZW5jb2RlLnY0NC5wcmltYXJ5X2Fzc2VtYmx5LmFubm90YXRpb24uZ3RmLmd6IgpndGZfaW49IiR7c291cmNlfS9nZW5jb2RlLnY0NC5wcmltYXJ5X2Fzc2VtYmx5LmFubm90YXRpb24uZ3RmIgoKaWYgWyAhIC1mICIkZ3RmX2luIiBdOyB0aGVuCiAgICBjdXJsIC1zUyAiJGd0Zl91cmwiIHwgemNhdCA+ICIkZ3RmX2luIgpmaQoKZ3JlcCAiXmNocjciICRndGZfaW4gfCBhd2sgJyQzID09ICJnZW5lIicgfCBzZWQgJ3MvLipnZW5lX25hbWUgIlwoW14iXSpcKSIuKi9cMS9nJyA+ICR7c291cmNlfS9nZW5jb2RlLnY0NF9nZW5lX25hbWVzX2NocjcudHh0CgpncmVwICJeY2hyWSIgJGd0Zl9pbiB8IGF3ayAnJDMgPT0gImdlbmUiJyB8IHNlZCAncy8uKmdlbmVfbmFtZSAiXChbXiJdKlwpIi4qL1wxL2cnID4gJHtzb3VyY2V9L2dlbmNvZGUudjQ0X2dlbmVfbmFtZXNfY2hyWS50eHQKYGBgCgpgYGB7cn0KY2hyN19nZW5lcyA8LQogIHJlYWRfbGluZXMoCiAgICAiL1VzZXJzL2hvbHplaGVucmlldHRhL0RvY3VtZW50cy9wZXJzb25hbC9waGRfYXBwbGljYXRpb25zL211ZHNfdGFza19tYXJyL3JlZmVyZW5jZV9zb3VyY2VzL2dlbmNvZGUudjQ0X2dlbmVfbmFtZXNfY2hyNy50eHQiCiAgKQpgYGAKClN1YnNldCB0byBnZW5lcyBleHByZXNzZWQgaW4gPj0gMTAlIG9mIGNlbGxzIGluIGRlbCg3KSBzY1JOQS1zZXEgZGF0YSB0byBzcGVlZCB1cCBhbmFseXNpcy4KCmBgYHtyfQoKYGBgCgoKIyMgU2F2ZSBTZXNzaW9uIEluZm8KCmBgYHtyfQp3cml0ZUxpbmVzKGNhcHR1cmUub3V0cHV0KHNlc3Npb25JbmZvKCkpLCBwYXN0ZTAoU3lzLkRhdGUoKSwgIl9zZXNzaW9uSW5mby50eHQiKSkKYGBgCgo=